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