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