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