]> BookStack Code Mirror - bookstack/blob - tests/Permissions/RolesTest.php
Cleaned testing service provider usage
[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 Illuminate\Testing\TestResponse;
16 use Tests\TestCase;
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/features')->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/features');
62         $this->withHtml($resp)->assertElementContains('a[href="' . url('/settings/roles') . '"]', 'Roles');
63
64         $resp = $this->get('/settings/roles');
65         $this->withHtml($resp)->assertElementContains('a[href="' . url('/settings/roles/new') . '"]', 'Create New Role');
66
67         $resp = $this->get('/settings/roles/new');
68         $this->withHtml($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         $this->withHtml($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         $this->withHtml($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         $this->withHtml($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         $this->withHtml($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_image_view_notice_shown_on_role_form()
167     {
168         /** @var Role $role */
169         $role = Role::query()->first();
170         $this->asAdmin()->get("/settings/roles/{$role->id}")
171             ->assertSee('Actual access of uploaded image files will be dependant upon system image storage option');
172     }
173
174     public function test_copy_role_button_shown()
175     {
176         /** @var Role $role */
177         $role = Role::query()->first();
178         $resp = $this->asAdmin()->get("/settings/roles/{$role->id}");
179         $this->withHtml($resp)->assertElementContains('a[href$="/roles/new?copy_from=' . $role->id . '"]', 'Copy');
180     }
181
182     public function test_copy_from_param_on_create_prefills_with_other_role_data()
183     {
184         /** @var Role $role */
185         $role = Role::query()->first();
186         $resp = $this->asAdmin()->get("/settings/roles/new?copy_from={$role->id}");
187         $resp->assertOk();
188         $this->withHtml($resp)->assertElementExists('input[name="display_name"][value="' . ($role->display_name . ' (Copy)') . '"]');
189     }
190
191     public function test_manage_user_permission()
192     {
193         $this->actingAs($this->user)->get('/settings/users')->assertRedirect('/');
194         $this->giveUserPermissions($this->user, ['users-manage']);
195         $this->actingAs($this->user)->get('/settings/users')->assertOk();
196     }
197
198     public function test_manage_users_permission_shows_link_in_header_if_does_not_have_settings_manage_permision()
199     {
200         $usersLink = 'href="' . url('/settings/users') . '"';
201         $this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
202         $this->giveUserPermissions($this->user, ['users-manage']);
203         $this->actingAs($this->user)->get('/')->assertSee($usersLink, false);
204         $this->giveUserPermissions($this->user, ['settings-manage', 'users-manage']);
205         $this->actingAs($this->user)->get('/')->assertDontSee($usersLink, false);
206     }
207
208     public function test_user_cannot_change_email_unless_they_have_manage_users_permission()
209     {
210         $userProfileUrl = '/settings/users/' . $this->user->id;
211         $originalEmail = $this->user->email;
212         $this->actingAs($this->user);
213
214         $resp = $this->get($userProfileUrl)
215             ->assertOk();
216         $this->withHtml($resp)->assertElementExists('input[name=email][disabled]');
217         $this->put($userProfileUrl, [
218             'name'  => 'my_new_name',
219             'email' => '[email protected]',
220         ]);
221         $this->assertDatabaseHas('users', [
222             'id'    => $this->user->id,
223             'email' => $originalEmail,
224             'name'  => 'my_new_name',
225         ]);
226
227         $this->giveUserPermissions($this->user, ['users-manage']);
228
229         $resp = $this->get($userProfileUrl)
230             ->assertOk();
231         $this->withHtml($resp)->assertElementNotExists('input[name=email][disabled]')
232             ->assertElementExists('input[name=email]');
233         $this->put($userProfileUrl, [
234             'name'  => 'my_new_name_2',
235             'email' => '[email protected]',
236         ]);
237
238         $this->assertDatabaseHas('users', [
239             'id'    => $this->user->id,
240             'email' => '[email protected]',
241             'name'  => 'my_new_name_2',
242         ]);
243     }
244
245     public function test_user_roles_manage_permission()
246     {
247         $this->actingAs($this->user)->get('/settings/roles')->assertRedirect('/');
248         $this->get('/settings/roles/1')->assertRedirect('/');
249         $this->giveUserPermissions($this->user, ['user-roles-manage']);
250         $this->actingAs($this->user)->get('/settings/roles')->assertOk();
251         $this->get('/settings/roles/1')
252             ->assertOk()
253             ->assertSee('Admin');
254     }
255
256     public function test_settings_manage_permission()
257     {
258         $this->actingAs($this->user)->get('/settings/features')->assertRedirect('/');
259         $this->giveUserPermissions($this->user, ['settings-manage']);
260         $this->get('/settings/features')->assertOk();
261
262         $resp = $this->post('/settings/features', []);
263         $resp->assertRedirect('/settings/features');
264         $resp = $this->get('/settings/features');
265         $resp->assertSee('Settings saved');
266     }
267
268     public function test_restrictions_manage_all_permission()
269     {
270         $page = Page::query()->get()->first();
271
272         $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
273         $this->get($page->getUrl('/permissions'))->assertRedirect('/');
274
275         $this->giveUserPermissions($this->user, ['restrictions-manage-all']);
276
277         $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
278
279         $this->get($page->getUrl('/permissions'))
280             ->assertOk()
281             ->assertSee('Page Permissions');
282     }
283
284     public function test_restrictions_manage_own_permission()
285     {
286         /** @var Page $otherUsersPage */
287         $otherUsersPage = Page::query()->first();
288         $content = $this->createEntityChainBelongingToUser($this->user);
289
290         // Set a different creator on the page we're checking to ensure
291         // that the owner fields are checked
292         $page = $content['page']; /** @var Page $page */
293         $page->created_by = $otherUsersPage->id;
294         $page->owned_by = $this->user->id;
295         $page->save();
296
297         // Check can't restrict other's content
298         $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
299         $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect('/');
300
301         // Check can't restrict own content
302         $this->actingAs($this->user)->get($page->getUrl())->assertDontSee('Permissions');
303         $this->get($page->getUrl('/permissions'))->assertRedirect('/');
304
305         $this->giveUserPermissions($this->user, ['restrictions-manage-own']);
306
307         // Check can't restrict other's content
308         $this->actingAs($this->user)->get($otherUsersPage->getUrl())->assertDontSee('Permissions');
309         $this->get($otherUsersPage->getUrl('/permissions'))->assertRedirect();
310
311         // Check can restrict own content
312         $this->actingAs($this->user)->get($page->getUrl())->assertSee('Permissions');
313         $this->get($page->getUrl('/permissions'))->assertOk();
314     }
315
316     /**
317      * Check a standard entity access permission.
318      */
319     private function checkAccessPermission(string $permission, array $accessUrls = [], array $visibles = [])
320     {
321         foreach ($accessUrls as $url) {
322             $this->actingAs($this->user)->get($url)->assertRedirect('/');
323         }
324
325         foreach ($visibles as $url => $text) {
326             $resp = $this->actingAs($this->user)->get($url);
327             $this->withHtml($resp)->assertElementNotContains('.action-buttons', $text);
328         }
329
330         $this->giveUserPermissions($this->user, [$permission]);
331
332         foreach ($accessUrls as $url) {
333             $this->actingAs($this->user)->get($url)->assertOk();
334         }
335         foreach ($visibles as $url => $text) {
336             $this->actingAs($this->user)->get($url)->assertSee($text);
337         }
338     }
339
340     public function test_bookshelves_create_all_permissions()
341     {
342         $this->checkAccessPermission('bookshelf-create-all', [
343             '/create-shelf',
344         ], [
345             '/shelves' => 'New Shelf',
346         ]);
347
348         $this->post('/shelves', [
349             'name'        => 'test shelf',
350             'description' => 'shelf desc',
351         ])->assertRedirect('/shelves/test-shelf');
352     }
353
354     public function test_bookshelves_edit_own_permission()
355     {
356         /** @var Bookshelf $otherShelf */
357         $otherShelf = Bookshelf::query()->first();
358         $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
359         $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
360         $this->regenEntityPermissions($ownShelf);
361
362         $this->checkAccessPermission('bookshelf-update-own', [
363             $ownShelf->getUrl('/edit'),
364         ], [
365             $ownShelf->getUrl() => 'Edit',
366         ]);
367
368         $resp = $this->get($otherShelf->getUrl());
369         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
370         $this->get($otherShelf->getUrl('/edit'))->assertRedirect('/');
371     }
372
373     public function test_bookshelves_edit_all_permission()
374     {
375         /** @var Bookshelf $otherShelf */
376         $otherShelf = Bookshelf::query()->first();
377         $this->checkAccessPermission('bookshelf-update-all', [
378             $otherShelf->getUrl('/edit'),
379         ], [
380             $otherShelf->getUrl() => 'Edit',
381         ]);
382     }
383
384     public function test_bookshelves_delete_own_permission()
385     {
386         $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
387         /** @var Bookshelf $otherShelf */
388         $otherShelf = Bookshelf::query()->first();
389         $ownShelf = $this->newShelf(['name' => 'test-shelf', 'slug' => 'test-shelf']);
390         $ownShelf->forceFill(['owned_by' => $this->user->id, 'updated_by' => $this->user->id])->save();
391         $this->regenEntityPermissions($ownShelf);
392
393         $this->checkAccessPermission('bookshelf-delete-own', [
394             $ownShelf->getUrl('/delete'),
395         ], [
396             $ownShelf->getUrl() => 'Delete',
397         ]);
398
399         $resp = $this->get($otherShelf->getUrl());
400         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Delete');
401         $this->get($otherShelf->getUrl('/delete'))->assertRedirect('/');
402
403         $this->get($ownShelf->getUrl());
404         $this->delete($ownShelf->getUrl())->assertRedirect('/shelves');
405         $this->get('/shelves')->assertDontSee($ownShelf->name);
406     }
407
408     public function test_bookshelves_delete_all_permission()
409     {
410         $this->giveUserPermissions($this->user, ['bookshelf-update-all']);
411         /** @var Bookshelf $otherShelf */
412         $otherShelf = Bookshelf::query()->first();
413         $this->checkAccessPermission('bookshelf-delete-all', [
414             $otherShelf->getUrl('/delete'),
415         ], [
416             $otherShelf->getUrl() => 'Delete',
417         ]);
418
419         $this->delete($otherShelf->getUrl())->assertRedirect('/shelves');
420         $this->get('/shelves')->assertDontSee($otherShelf->name);
421     }
422
423     public function test_books_create_all_permissions()
424     {
425         $this->checkAccessPermission('book-create-all', [
426             '/create-book',
427         ], [
428             '/books' => 'Create New Book',
429         ]);
430
431         $this->post('/books', [
432             'name'        => 'test book',
433             'description' => 'book desc',
434         ])->assertRedirect('/books/test-book');
435     }
436
437     public function test_books_edit_own_permission()
438     {
439         /** @var Book $otherBook */
440         $otherBook = Book::query()->take(1)->get()->first();
441         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
442         $this->checkAccessPermission('book-update-own', [
443             $ownBook->getUrl() . '/edit',
444         ], [
445             $ownBook->getUrl() => 'Edit',
446         ]);
447
448         $resp = $this->get($otherBook->getUrl());
449         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
450         $this->get($otherBook->getUrl('/edit'))->assertRedirect('/');
451     }
452
453     public function test_books_edit_all_permission()
454     {
455         /** @var Book $otherBook */
456         $otherBook = Book::query()->take(1)->get()->first();
457         $this->checkAccessPermission('book-update-all', [
458             $otherBook->getUrl() . '/edit',
459         ], [
460             $otherBook->getUrl() => 'Edit',
461         ]);
462     }
463
464     public function test_books_delete_own_permission()
465     {
466         $this->giveUserPermissions($this->user, ['book-update-all']);
467         /** @var Book $otherBook */
468         $otherBook = Book::query()->take(1)->get()->first();
469         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
470         $this->checkAccessPermission('book-delete-own', [
471             $ownBook->getUrl() . '/delete',
472         ], [
473             $ownBook->getUrl() => 'Delete',
474         ]);
475
476         $resp = $this->get($otherBook->getUrl());
477         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Delete');
478         $this->get($otherBook->getUrl('/delete'))->assertRedirect('/');
479         $this->get($ownBook->getUrl());
480         $this->delete($ownBook->getUrl())->assertRedirect('/books');
481         $this->get('/books')->assertDontSee($ownBook->name);
482     }
483
484     public function test_books_delete_all_permission()
485     {
486         $this->giveUserPermissions($this->user, ['book-update-all']);
487         /** @var Book $otherBook */
488         $otherBook = Book::query()->take(1)->get()->first();
489         $this->checkAccessPermission('book-delete-all', [
490             $otherBook->getUrl() . '/delete',
491         ], [
492             $otherBook->getUrl() => 'Delete',
493         ]);
494
495         $this->get($otherBook->getUrl());
496         $this->delete($otherBook->getUrl())->assertRedirect('/books');
497         $this->get('/books')->assertDontSee($otherBook->name);
498     }
499
500     public function test_chapter_create_own_permissions()
501     {
502         /** @var Book $book */
503         $book = Book::query()->take(1)->get()->first();
504         $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
505         $this->checkAccessPermission('chapter-create-own', [
506             $ownBook->getUrl('/create-chapter'),
507         ], [
508             $ownBook->getUrl() => 'New Chapter',
509         ]);
510
511         $this->post($ownBook->getUrl('/create-chapter'), [
512             'name'        => 'test chapter',
513             'description' => 'chapter desc',
514         ])->assertRedirect($ownBook->getUrl('/chapter/test-chapter'));
515
516         $resp = $this->get($book->getUrl());
517         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'New Chapter');
518         $this->get($book->getUrl('/create-chapter'))->assertRedirect('/');
519     }
520
521     public function test_chapter_create_all_permissions()
522     {
523         /** @var Book $book */
524         $book = Book::query()->first();
525         $this->checkAccessPermission('chapter-create-all', [
526             $book->getUrl('/create-chapter'),
527         ], [
528             $book->getUrl() => 'New Chapter',
529         ]);
530
531         $this->post($book->getUrl('/create-chapter'), [
532             'name'        => 'test chapter',
533             'description' => 'chapter desc',
534         ])->assertRedirect($book->getUrl('/chapter/test-chapter'));
535     }
536
537     public function test_chapter_edit_own_permission()
538     {
539         /** @var Chapter $otherChapter */
540         $otherChapter = Chapter::query()->first();
541         $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
542         $this->checkAccessPermission('chapter-update-own', [
543             $ownChapter->getUrl() . '/edit',
544         ], [
545             $ownChapter->getUrl() => 'Edit',
546         ]);
547
548         $resp = $this->get($otherChapter->getUrl());
549         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
550         $this->get($otherChapter->getUrl('/edit'))->assertRedirect('/');
551     }
552
553     public function test_chapter_edit_all_permission()
554     {
555         /** @var Chapter $otherChapter */
556         $otherChapter = Chapter::query()->take(1)->get()->first();
557         $this->checkAccessPermission('chapter-update-all', [
558             $otherChapter->getUrl() . '/edit',
559         ], [
560             $otherChapter->getUrl() => 'Edit',
561         ]);
562     }
563
564     public function test_chapter_delete_own_permission()
565     {
566         $this->giveUserPermissions($this->user, ['chapter-update-all']);
567         /** @var Chapter $otherChapter */
568         $otherChapter = Chapter::query()->first();
569         $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
570         $this->checkAccessPermission('chapter-delete-own', [
571             $ownChapter->getUrl() . '/delete',
572         ], [
573             $ownChapter->getUrl() => 'Delete',
574         ]);
575
576         $bookUrl = $ownChapter->book->getUrl();
577         $resp = $this->get($otherChapter->getUrl());
578         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Delete');
579         $this->get($otherChapter->getUrl('/delete'))->assertRedirect('/');
580         $this->get($ownChapter->getUrl());
581         $this->delete($ownChapter->getUrl())->assertRedirect($bookUrl);
582         $resp = $this->get($bookUrl);
583         $this->withHtml($resp)->assertElementNotContains('.book-content', $ownChapter->name);
584     }
585
586     public function test_chapter_delete_all_permission()
587     {
588         $this->giveUserPermissions($this->user, ['chapter-update-all']);
589         /** @var Chapter $otherChapter */
590         $otherChapter = Chapter::query()->first();
591         $this->checkAccessPermission('chapter-delete-all', [
592             $otherChapter->getUrl() . '/delete',
593         ], [
594             $otherChapter->getUrl() => 'Delete',
595         ]);
596
597         $bookUrl = $otherChapter->book->getUrl();
598         $this->get($otherChapter->getUrl());
599         $this->delete($otherChapter->getUrl())->assertRedirect($bookUrl);
600         $resp = $this->get($bookUrl);
601         $this->withHtml($resp)->assertElementNotContains('.book-content', $otherChapter->name);
602     }
603
604     public function test_page_create_own_permissions()
605     {
606         /** @var Book $book */
607         $book = Book::query()->first();
608         /** @var Chapter $chapter */
609         $chapter = Chapter::query()->first();
610
611         $entities = $this->createEntityChainBelongingToUser($this->user);
612         $ownBook = $entities['book'];
613         $ownChapter = $entities['chapter'];
614
615         $createUrl = $ownBook->getUrl('/create-page');
616         $createUrlChapter = $ownChapter->getUrl('/create-page');
617         $accessUrls = [$createUrl, $createUrlChapter];
618
619         foreach ($accessUrls as $url) {
620             $this->actingAs($this->user)->get($url)->assertRedirect('/');
621         }
622
623         $this->checkAccessPermission('page-create-own', [], [
624             $ownBook->getUrl()    => 'New Page',
625             $ownChapter->getUrl() => 'New Page',
626         ]);
627
628         $this->giveUserPermissions($this->user, ['page-create-own']);
629
630         foreach ($accessUrls as $index => $url) {
631             $resp = $this->actingAs($this->user)->get($url);
632             $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
633             $resp->assertRedirect($expectedUrl);
634         }
635
636         $this->get($createUrl);
637         /** @var Page $draft */
638         $draft = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
639         $this->post($draft->getUrl(), [
640             'name' => 'test page',
641             'html' => 'page desc',
642         ])->assertRedirect($ownBook->getUrl('/page/test-page'));
643
644         $resp = $this->get($book->getUrl());
645         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'New Page');
646         $this->get($book->getUrl('/create-page'))->assertRedirect('/');
647
648         $resp = $this->get($chapter->getUrl());
649         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'New Page');
650         $this->get($chapter->getUrl('/create-page'))->assertRedirect('/');
651     }
652
653     public function test_page_create_all_permissions()
654     {
655         /** @var Book $book */
656         $book = Book::query()->first();
657         /** @var Chapter $chapter */
658         $chapter = Chapter::query()->first();
659         $createUrl = $book->getUrl('/create-page');
660
661         $createUrlChapter = $chapter->getUrl('/create-page');
662         $accessUrls = [$createUrl, $createUrlChapter];
663
664         foreach ($accessUrls as $url) {
665             $this->actingAs($this->user)->get($url)->assertRedirect('/');
666         }
667
668         $this->checkAccessPermission('page-create-all', [], [
669             $book->getUrl()    => 'New Page',
670             $chapter->getUrl() => 'New Page',
671         ]);
672
673         $this->giveUserPermissions($this->user, ['page-create-all']);
674
675         foreach ($accessUrls as $index => $url) {
676             $resp = $this->actingAs($this->user)->get($url);
677             $expectedUrl = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
678             $resp->assertRedirect($expectedUrl);
679         }
680
681         $this->get($createUrl);
682         /** @var Page $draft */
683         $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
684         $this->post($draft->getUrl(), [
685             'name' => 'test page',
686             'html' => 'page desc',
687         ])->assertRedirect($book->getUrl('/page/test-page'));
688
689         $this->get($chapter->getUrl('/create-page'));
690         /** @var Page $draft */
691         $draft = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
692         $this->post($draft->getUrl(), [
693             'name' => 'new test page',
694             'html' => 'page desc',
695         ])->assertRedirect($book->getUrl('/page/new-test-page'));
696     }
697
698     public function test_page_edit_own_permission()
699     {
700         /** @var Page $otherPage */
701         $otherPage = Page::query()->first();
702         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
703         $this->checkAccessPermission('page-update-own', [
704             $ownPage->getUrl() . '/edit',
705         ], [
706             $ownPage->getUrl() => 'Edit',
707         ]);
708
709         $resp = $this->get($otherPage->getUrl());
710         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Edit');
711         $this->get($otherPage->getUrl() . '/edit')->assertRedirect('/');
712     }
713
714     public function test_page_edit_all_permission()
715     {
716         /** @var Page $otherPage */
717         $otherPage = Page::query()->first();
718         $this->checkAccessPermission('page-update-all', [
719             $otherPage->getUrl('/edit'),
720         ], [
721             $otherPage->getUrl() => 'Edit',
722         ]);
723     }
724
725     public function test_page_delete_own_permission()
726     {
727         $this->giveUserPermissions($this->user, ['page-update-all']);
728         /** @var Page $otherPage */
729         $otherPage = Page::query()->first();
730         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
731         $this->checkAccessPermission('page-delete-own', [
732             $ownPage->getUrl() . '/delete',
733         ], [
734             $ownPage->getUrl() => 'Delete',
735         ]);
736
737         $parent = $ownPage->chapter ?? $ownPage->book;
738         $resp = $this->get($otherPage->getUrl());
739         $this->withHtml($resp)->assertElementNotContains('.action-buttons', 'Delete');
740         $this->get($otherPage->getUrl('/delete'))->assertRedirect('/');
741         $this->get($ownPage->getUrl());
742         $this->delete($ownPage->getUrl())->assertRedirect($parent->getUrl());
743         $resp = $this->get($parent->getUrl());
744         $this->withHtml($resp)->assertElementNotContains('.book-content', $ownPage->name);
745     }
746
747     public function test_page_delete_all_permission()
748     {
749         $this->giveUserPermissions($this->user, ['page-update-all']);
750         /** @var Page $otherPage */
751         $otherPage = Page::query()->first();
752
753         $this->checkAccessPermission('page-delete-all', [
754             $otherPage->getUrl() . '/delete',
755         ], [
756             $otherPage->getUrl() => 'Delete',
757         ]);
758
759         /** @var Entity $parent */
760         $parent = $otherPage->chapter ?? $otherPage->book;
761         $this->get($otherPage->getUrl());
762
763         $this->delete($otherPage->getUrl())->assertRedirect($parent->getUrl());
764         $this->get($parent->getUrl())->assertDontSee($otherPage->name);
765     }
766
767     public function test_public_role_visible_in_user_edit_screen()
768     {
769         /** @var User $user */
770         $user = User::query()->first();
771         $adminRole = Role::getSystemRole('admin');
772         $publicRole = Role::getSystemRole('public');
773         $resp = $this->asAdmin()->get('/settings/users/' . $user->id);
774         $this->withHtml($resp)->assertElementExists('[name="roles[' . $adminRole->id . ']"]')
775             ->assertElementExists('[name="roles[' . $publicRole->id . ']"]');
776     }
777
778     public function test_public_role_visible_in_role_listing()
779     {
780         $this->asAdmin()->get('/settings/roles')
781             ->assertSee('Admin')
782             ->assertSee('Public');
783     }
784
785     public function test_public_role_visible_in_default_role_setting()
786     {
787         $resp = $this->asAdmin()->get('/settings/registration');
788         $this->withHtml($resp)->assertElementExists('[data-system-role-name="admin"]')
789             ->assertElementExists('[data-system-role-name="public"]');
790     }
791
792     public function test_public_role_not_deletable()
793     {
794         /** @var Role $publicRole */
795         $publicRole = Role::getSystemRole('public');
796         $resp = $this->asAdmin()->delete('/settings/roles/delete/' . $publicRole->id);
797         $resp->assertRedirect('/');
798
799         $this->get('/settings/roles/delete/' . $publicRole->id);
800         $resp = $this->delete('/settings/roles/delete/' . $publicRole->id);
801         $resp->assertRedirect('/settings/roles/delete/' . $publicRole->id);
802         $resp = $this->get('/settings/roles/delete/' . $publicRole->id);
803         $resp->assertSee('This role is a system role and cannot be deleted');
804     }
805
806     public function test_image_delete_own_permission()
807     {
808         $this->giveUserPermissions($this->user, ['image-update-all']);
809         /** @var Page $page */
810         $page = Page::query()->first();
811         $image = Image::factory()->create([
812             'uploaded_to' => $page->id,
813             'created_by'  => $this->user->id,
814             'updated_by'  => $this->user->id,
815         ]);
816
817         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
818
819         $this->giveUserPermissions($this->user, ['image-delete-own']);
820
821         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
822         $this->assertDatabaseMissing('images', ['id' => $image->id]);
823     }
824
825     public function test_image_delete_all_permission()
826     {
827         $this->giveUserPermissions($this->user, ['image-update-all']);
828         $admin = $this->getAdmin();
829         /** @var Page $page */
830         $page = Page::query()->first();
831         $image = Image::factory()->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
832
833         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
834
835         $this->giveUserPermissions($this->user, ['image-delete-own']);
836
837         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertStatus(403);
838
839         $this->giveUserPermissions($this->user, ['image-delete-all']);
840
841         $this->actingAs($this->user)->json('delete', '/images/' . $image->id)->assertOk();
842         $this->assertDatabaseMissing('images', ['id' => $image->id]);
843     }
844
845     public function test_role_permission_removal()
846     {
847         // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
848         /** @var Page $page */
849         $page = Page::query()->first();
850         $viewerRole = Role::getRole('viewer');
851         $viewer = $this->getViewer();
852         $this->actingAs($viewer)->get($page->getUrl())->assertOk();
853
854         $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
855             'display_name' => $viewerRole->display_name,
856             'description'  => $viewerRole->description,
857             'permission'   => [],
858         ])->assertStatus(302);
859
860         $this->actingAs($viewer)->get($page->getUrl())->assertStatus(404);
861     }
862
863     public function test_empty_state_actions_not_visible_without_permission()
864     {
865         $admin = $this->getAdmin();
866         // Book links
867         $book = Book::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
868         $this->regenEntityPermissions($book);
869         $this->actingAs($this->getViewer())->get($book->getUrl())
870             ->assertDontSee('Create a new page')
871             ->assertDontSee('Add a chapter');
872
873         // Chapter links
874         $chapter = Chapter::factory()->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
875         $this->regenEntityPermissions($chapter);
876         $this->actingAs($this->getViewer())->get($chapter->getUrl())
877             ->assertDontSee('Create a new page')
878             ->assertDontSee('Sort the current book');
879     }
880
881     public function test_comment_create_permission()
882     {
883         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
884
885         $this->actingAs($this->user)
886             ->addComment($ownPage)
887             ->assertStatus(403);
888
889         $this->giveUserPermissions($this->user, ['comment-create-all']);
890
891         $this->actingAs($this->user)
892             ->addComment($ownPage)
893             ->assertOk();
894     }
895
896     public function test_comment_update_own_permission()
897     {
898         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
899         $this->giveUserPermissions($this->user, ['comment-create-all']);
900         $this->actingAs($this->user)->addComment($ownPage);
901         /** @var Comment $comment */
902         $comment = $ownPage->comments()->latest()->first();
903
904         // no comment-update-own
905         $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
906
907         $this->giveUserPermissions($this->user, ['comment-update-own']);
908
909         // now has comment-update-own
910         $this->actingAs($this->user)->updateComment($comment)->assertOk();
911     }
912
913     public function test_comment_update_all_permission()
914     {
915         /** @var Page $ownPage */
916         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
917         $this->asAdmin()->addComment($ownPage);
918         /** @var Comment $comment */
919         $comment = $ownPage->comments()->latest()->first();
920
921         // no comment-update-all
922         $this->actingAs($this->user)->updateComment($comment)->assertStatus(403);
923
924         $this->giveUserPermissions($this->user, ['comment-update-all']);
925
926         // now has comment-update-all
927         $this->actingAs($this->user)->updateComment($comment)->assertOk();
928     }
929
930     public function test_comment_delete_own_permission()
931     {
932         /** @var Page $ownPage */
933         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
934         $this->giveUserPermissions($this->user, ['comment-create-all']);
935         $this->actingAs($this->user)->addComment($ownPage);
936
937         /** @var Comment $comment */
938         $comment = $ownPage->comments()->latest()->first();
939
940         // no comment-delete-own
941         $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
942
943         $this->giveUserPermissions($this->user, ['comment-delete-own']);
944
945         // now has comment-update-own
946         $this->actingAs($this->user)->deleteComment($comment)->assertOk();
947     }
948
949     public function test_comment_delete_all_permission()
950     {
951         /** @var Page $ownPage */
952         $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
953         $this->asAdmin()->addComment($ownPage);
954         /** @var Comment $comment */
955         $comment = $ownPage->comments()->latest()->first();
956
957         // no comment-delete-all
958         $this->actingAs($this->user)->deleteComment($comment)->assertStatus(403);
959
960         $this->giveUserPermissions($this->user, ['comment-delete-all']);
961
962         // now has comment-delete-all
963         $this->actingAs($this->user)->deleteComment($comment)->assertOk();
964     }
965
966     private function addComment(Page $page): TestResponse
967     {
968         $comment = Comment::factory()->make();
969
970         return $this->postJson("/comment/$page->id", $comment->only('text', 'html'));
971     }
972
973     private function updateComment(Comment $comment): TestResponse
974     {
975         $commentData = Comment::factory()->make();
976
977         return $this->putJson("/comment/{$comment->id}", $commentData->only('text', 'html'));
978     }
979
980     private function deleteComment(Comment $comment): TestResponse
981     {
982         return $this->json('DELETE', '/comment/' . $comment->id);
983     }
984 }