4 use BookStack\Repos\PermissionsRepo;
6 use Laravel\BrowserKitTesting\HttpException;
7 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
9 class RolesTest extends BrowserKitTest
13 public function setUp()
16 $this->user = $this->getViewer();
20 * Give the given user some permissions.
21 * @param \BookStack\User $user
22 * @param array $permissions
24 protected function giveUserPermissions(\BookStack\User $user, $permissions = [])
26 $newRole = $this->createNewRole($permissions);
27 $user->attachRole($newRole);
29 $user->permissions(false);
33 * Create a new basic role for testing purposes.
34 * @param array $permissions
37 protected function createNewRole($permissions = [])
39 $permissionRepo = app(PermissionsRepo::class);
40 $roleData = factory(\BookStack\Role::class)->make()->toArray();
41 $roleData['permissions'] = array_flip($permissions);
42 return $permissionRepo->saveNewRole($roleData);
45 public function test_admin_can_see_settings()
47 $this->asAdmin()->visit('/settings')->see('Settings');
50 public function test_cannot_delete_admin_role()
52 $adminRole = \BookStack\Role::getRole('admin');
53 $deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
54 $this->asAdmin()->visit($deletePageUrl)
56 ->seePageIs($deletePageUrl)
57 ->see('cannot be deleted');
60 public function test_role_cannot_be_deleted_if_default()
62 $newRole = $this->createNewRole();
63 $this->setSettings(['registration-role' => $newRole->id]);
65 $deletePageUrl = '/settings/roles/delete/' . $newRole->id;
66 $this->asAdmin()->visit($deletePageUrl)
68 ->seePageIs($deletePageUrl)
69 ->see('cannot be deleted');
72 public function test_role_create_update_delete_flow()
74 $testRoleName = 'Test Role';
75 $testRoleDesc = 'a little test description';
76 $testRoleUpdateName = 'An Super Updated role';
79 $this->asAdmin()->visit('/settings')
81 ->seePageIs('/settings/roles')
82 ->click('Create New Role')
83 ->type('Test Role', 'display_name')
84 ->type('A little test description', 'description')
86 ->seeInDatabase('roles', ['display_name' => $testRoleName, 'name' => 'test-role', 'description' => $testRoleDesc])
87 ->seePageIs('/settings/roles');
89 $this->asAdmin()->visit('/settings/roles')
91 ->click($testRoleName)
92 ->type($testRoleUpdateName, '#display_name')
94 ->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'name' => 'test-role', 'description' => $testRoleDesc])
95 ->seePageIs('/settings/roles');
97 $this->asAdmin()->visit('/settings/roles')
98 ->click($testRoleUpdateName)
99 ->click('Delete Role')
100 ->see($testRoleUpdateName)
102 ->seePageIs('/settings/roles')
103 ->dontSee($testRoleUpdateName);
106 public function test_manage_user_permission()
108 $this->actingAs($this->user)->visit('/settings/users')
110 $this->giveUserPermissions($this->user, ['users-manage']);
111 $this->actingAs($this->user)->visit('/settings/users')
112 ->seePageIs('/settings/users');
115 public function test_user_roles_manage_permission()
117 $this->actingAs($this->user)->visit('/settings/roles')
118 ->seePageIs('/')->visit('/settings/roles/1')->seePageIs('/');
119 $this->giveUserPermissions($this->user, ['user-roles-manage']);
120 $this->actingAs($this->user)->visit('/settings/roles')
121 ->seePageIs('/settings/roles')->click('Admin')
125 public function test_settings_manage_permission()
127 $this->actingAs($this->user)->visit('/settings')
129 $this->giveUserPermissions($this->user, ['settings-manage']);
130 $this->actingAs($this->user)->visit('/settings')
131 ->seePageIs('/settings')->press('Save Settings')->see('Settings Saved');
134 public function test_restrictions_manage_all_permission()
136 $page = \BookStack\Page::take(1)->get()->first();
137 $this->actingAs($this->user)->visit($page->getUrl())
138 ->dontSee('Permissions')
139 ->visit($page->getUrl() . '/permissions')
141 $this->giveUserPermissions($this->user, ['restrictions-manage-all']);
142 $this->actingAs($this->user)->visit($page->getUrl())
144 ->click('Permissions')
145 ->see('Page Permissions')->seePageIs($page->getUrl() . '/permissions');
148 public function test_restrictions_manage_own_permission()
150 $otherUsersPage = \BookStack\Page::first();
151 $content = $this->createEntityChainBelongingToUser($this->user);
152 // Check can't restrict other's content
153 $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
154 ->dontSee('Permissions')
155 ->visit($otherUsersPage->getUrl() . '/permissions')
157 // Check can't restrict own content
158 $this->actingAs($this->user)->visit($content['page']->getUrl())
159 ->dontSee('Permissions')
160 ->visit($content['page']->getUrl() . '/permissions')
163 $this->giveUserPermissions($this->user, ['restrictions-manage-own']);
165 // Check can't restrict other's content
166 $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
167 ->dontSee('Permissions')
168 ->visit($otherUsersPage->getUrl() . '/permissions')
170 // Check can restrict own content
171 $this->actingAs($this->user)->visit($content['page']->getUrl())
173 ->click('Permissions')
174 ->seePageIs($content['page']->getUrl() . '/permissions');
178 * Check a standard entity access permission
179 * @param string $permission
180 * @param array $accessUrls Urls that are only accessible after having the permission
181 * @param array $visibles Check this text, In the buttons toolbar, is only visible with the permission
183 private function checkAccessPermission($permission, $accessUrls = [], $visibles = [])
185 foreach ($accessUrls as $url) {
186 $this->actingAs($this->user)->visit($url)
189 foreach ($visibles as $url => $text) {
190 $this->actingAs($this->user)->visit($url)
191 ->dontSeeInElement('.action-buttons',$text);
194 $this->giveUserPermissions($this->user, [$permission]);
196 foreach ($accessUrls as $url) {
197 $this->actingAs($this->user)->visit($url)
200 foreach ($visibles as $url => $text) {
201 $this->actingAs($this->user)->visit($url)
206 public function test_books_create_all_permissions()
208 $this->checkAccessPermission('book-create-all', [
211 '/books' => 'Create New Book'
214 $this->visit('/create-book')
215 ->type('test book', 'name')
216 ->type('book desc', 'description')
218 ->seePageIs('/books/test-book');
221 public function test_books_edit_own_permission()
223 $otherBook = \BookStack\Book::take(1)->get()->first();
224 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
225 $this->checkAccessPermission('book-update-own', [
226 $ownBook->getUrl() . '/edit'
228 $ownBook->getUrl() => 'Edit'
231 $this->visit($otherBook->getUrl())
232 ->dontSeeInElement('.action-buttons', 'Edit')
233 ->visit($otherBook->getUrl() . '/edit')
237 public function test_books_edit_all_permission()
239 $otherBook = \BookStack\Book::take(1)->get()->first();
240 $this->checkAccessPermission('book-update-all', [
241 $otherBook->getUrl() . '/edit'
243 $otherBook->getUrl() => 'Edit'
247 public function test_books_delete_own_permission()
249 $this->giveUserPermissions($this->user, ['book-update-all']);
250 $otherBook = \BookStack\Book::take(1)->get()->first();
251 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
252 $this->checkAccessPermission('book-delete-own', [
253 $ownBook->getUrl() . '/delete'
255 $ownBook->getUrl() => 'Delete'
258 $this->visit($otherBook->getUrl())
259 ->dontSeeInElement('.action-buttons', 'Delete')
260 ->visit($otherBook->getUrl() . '/delete')
262 $this->visit($ownBook->getUrl())->visit($ownBook->getUrl() . '/delete')
264 ->seePageIs('/books')
265 ->dontSee($ownBook->name);
268 public function test_books_delete_all_permission()
270 $this->giveUserPermissions($this->user, ['book-update-all']);
271 $otherBook = \BookStack\Book::take(1)->get()->first();
272 $this->checkAccessPermission('book-delete-all', [
273 $otherBook->getUrl() . '/delete'
275 $otherBook->getUrl() => 'Delete'
278 $this->visit($otherBook->getUrl())->visit($otherBook->getUrl() . '/delete')
280 ->seePageIs('/books')
281 ->dontSee($otherBook->name);
284 public function test_chapter_create_own_permissions()
286 $book = \BookStack\Book::take(1)->get()->first();
287 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
288 $this->checkAccessPermission('chapter-create-own', [
289 $ownBook->getUrl('/create-chapter')
291 $ownBook->getUrl() => 'New Chapter'
294 $this->visit($ownBook->getUrl('/create-chapter'))
295 ->type('test chapter', 'name')
296 ->type('chapter desc', 'description')
297 ->press('Save Chapter')
298 ->seePageIs($ownBook->getUrl('/chapter/test-chapter'));
300 $this->visit($book->getUrl())
301 ->dontSeeInElement('.action-buttons', 'New Chapter')
302 ->visit($book->getUrl('/create-chapter'))
306 public function test_chapter_create_all_permissions()
308 $book = \BookStack\Book::take(1)->get()->first();
309 $this->checkAccessPermission('chapter-create-all', [
310 $book->getUrl('/create-chapter')
312 $book->getUrl() => 'New Chapter'
315 $this->visit($book->getUrl('/create-chapter'))
316 ->type('test chapter', 'name')
317 ->type('chapter desc', 'description')
318 ->press('Save Chapter')
319 ->seePageIs($book->getUrl('/chapter/test-chapter'));
322 public function test_chapter_edit_own_permission()
324 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
325 $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
326 $this->checkAccessPermission('chapter-update-own', [
327 $ownChapter->getUrl() . '/edit'
329 $ownChapter->getUrl() => 'Edit'
332 $this->visit($otherChapter->getUrl())
333 ->dontSeeInElement('.action-buttons', 'Edit')
334 ->visit($otherChapter->getUrl() . '/edit')
338 public function test_chapter_edit_all_permission()
340 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
341 $this->checkAccessPermission('chapter-update-all', [
342 $otherChapter->getUrl() . '/edit'
344 $otherChapter->getUrl() => 'Edit'
348 public function test_chapter_delete_own_permission()
350 $this->giveUserPermissions($this->user, ['chapter-update-all']);
351 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
352 $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
353 $this->checkAccessPermission('chapter-delete-own', [
354 $ownChapter->getUrl() . '/delete'
356 $ownChapter->getUrl() => 'Delete'
359 $bookUrl = $ownChapter->book->getUrl();
360 $this->visit($otherChapter->getUrl())
361 ->dontSeeInElement('.action-buttons', 'Delete')
362 ->visit($otherChapter->getUrl() . '/delete')
364 $this->visit($ownChapter->getUrl())->visit($ownChapter->getUrl() . '/delete')
366 ->seePageIs($bookUrl)
367 ->dontSeeInElement('.book-content', $ownChapter->name);
370 public function test_chapter_delete_all_permission()
372 $this->giveUserPermissions($this->user, ['chapter-update-all']);
373 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
374 $this->checkAccessPermission('chapter-delete-all', [
375 $otherChapter->getUrl() . '/delete'
377 $otherChapter->getUrl() => 'Delete'
380 $bookUrl = $otherChapter->book->getUrl();
381 $this->visit($otherChapter->getUrl())->visit($otherChapter->getUrl() . '/delete')
383 ->seePageIs($bookUrl)
384 ->dontSeeInElement('.book-content', $otherChapter->name);
387 public function test_page_create_own_permissions()
389 $book = \BookStack\Book::first();
390 $chapter = \BookStack\Chapter::first();
392 $entities = $this->createEntityChainBelongingToUser($this->user);
393 $ownBook = $entities['book'];
394 $ownChapter = $entities['chapter'];
396 $createUrl = $ownBook->getUrl('/create-page');
397 $createUrlChapter = $ownChapter->getUrl('/create-page');
398 $accessUrls = [$createUrl, $createUrlChapter];
400 foreach ($accessUrls as $url) {
401 $this->actingAs($this->user)->visit($url)
405 $this->checkAccessPermission('page-create-own', [], [
406 $ownBook->getUrl() => 'New Page',
407 $ownChapter->getUrl() => 'New Page'
410 $this->giveUserPermissions($this->user, ['page-create-own']);
412 foreach ($accessUrls as $index => $url) {
413 $this->actingAs($this->user)->visit($url);
414 $expectedUrl = \BookStack\Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
415 $this->seePageIs($expectedUrl);
418 $this->visit($createUrl)
419 ->type('test page', 'name')
420 ->type('page desc', 'html')
422 ->seePageIs($ownBook->getUrl('/page/test-page'));
424 $this->visit($book->getUrl())
425 ->dontSeeInElement('.action-buttons', 'New Page')
426 ->visit($book->getUrl() . '/create-page')
428 $this->visit($chapter->getUrl())
429 ->dontSeeInElement('.action-buttons', 'New Page')
430 ->visit($chapter->getUrl() . '/create-page')
434 public function test_page_create_all_permissions()
436 $book = \BookStack\Book::take(1)->get()->first();
437 $chapter = \BookStack\Chapter::take(1)->get()->first();
438 $baseUrl = $book->getUrl() . '/page';
439 $createUrl = $book->getUrl('/create-page');
441 $createUrlChapter = $chapter->getUrl('/create-page');
442 $accessUrls = [$createUrl, $createUrlChapter];
444 foreach ($accessUrls as $url) {
445 $this->actingAs($this->user)->visit($url)
449 $this->checkAccessPermission('page-create-all', [], [
450 $book->getUrl() => 'New Page',
451 $chapter->getUrl() => 'New Page'
454 $this->giveUserPermissions($this->user, ['page-create-all']);
456 foreach ($accessUrls as $index => $url) {
457 $this->actingAs($this->user)->visit($url);
458 $expectedUrl = \BookStack\Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
459 $this->seePageIs($expectedUrl);
462 $this->visit($createUrl)
463 ->type('test page', 'name')
464 ->type('page desc', 'html')
466 ->seePageIs($book->getUrl('/page/test-page'));
468 $this->visit($chapter->getUrl('/create-page'))
469 ->type('new test page', 'name')
470 ->type('page desc', 'html')
472 ->seePageIs($book->getUrl('/page/new-test-page'));
475 public function test_page_edit_own_permission()
477 $otherPage = \BookStack\Page::take(1)->get()->first();
478 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
479 $this->checkAccessPermission('page-update-own', [
480 $ownPage->getUrl() . '/edit'
482 $ownPage->getUrl() => 'Edit'
485 $this->visit($otherPage->getUrl())
486 ->dontSeeInElement('.action-buttons', 'Edit')
487 ->visit($otherPage->getUrl() . '/edit')
491 public function test_page_edit_all_permission()
493 $otherPage = \BookStack\Page::take(1)->get()->first();
494 $this->checkAccessPermission('page-update-all', [
495 $otherPage->getUrl() . '/edit'
497 $otherPage->getUrl() => 'Edit'
501 public function test_page_delete_own_permission()
503 $this->giveUserPermissions($this->user, ['page-update-all']);
504 $otherPage = \BookStack\Page::take(1)->get()->first();
505 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
506 $this->checkAccessPermission('page-delete-own', [
507 $ownPage->getUrl() . '/delete'
509 $ownPage->getUrl() => 'Delete'
512 $bookUrl = $ownPage->book->getUrl();
513 $this->visit($otherPage->getUrl())
514 ->dontSeeInElement('.action-buttons', 'Delete')
515 ->visit($otherPage->getUrl() . '/delete')
517 $this->visit($ownPage->getUrl())->visit($ownPage->getUrl() . '/delete')
519 ->seePageIs($bookUrl)
520 ->dontSeeInElement('.book-content', $ownPage->name);
523 public function test_page_delete_all_permission()
525 $this->giveUserPermissions($this->user, ['page-update-all']);
526 $otherPage = \BookStack\Page::take(1)->get()->first();
527 $this->checkAccessPermission('page-delete-all', [
528 $otherPage->getUrl() . '/delete'
530 $otherPage->getUrl() => 'Delete'
533 $bookUrl = $otherPage->book->getUrl();
534 $this->visit($otherPage->getUrl())->visit($otherPage->getUrl() . '/delete')
536 ->seePageIs($bookUrl)
537 ->dontSeeInElement('.book-content', $otherPage->name);
540 public function test_public_role_visible_in_user_edit_screen()
542 $user = \BookStack\User::first();
543 $this->asAdmin()->visit('/settings/users/' . $user->id)
544 ->seeElement('#roles-admin')
545 ->seeElement('#roles-public');
548 public function test_public_role_visible_in_role_listing()
550 $this->asAdmin()->visit('/settings/roles')
555 public function test_public_role_visible_in_default_role_setting()
557 $this->asAdmin()->visit('/settings')
558 ->seeElement('[data-role-name="admin"]')
559 ->seeElement('[data-role-name="public"]');
563 public function test_public_role_not_deleteable()
565 $this->asAdmin()->visit('/settings/roles')
568 ->click('Delete Role')
571 ->see('Cannot be deleted');
574 public function test_image_delete_own_permission()
576 $this->giveUserPermissions($this->user, ['image-update-all']);
577 $page = \BookStack\Page::first();
578 $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $this->user->id, 'updated_by' => $this->user->id]);
580 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
581 ->seeStatusCode(403);
583 $this->giveUserPermissions($this->user, ['image-delete-own']);
585 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
587 ->dontSeeInDatabase('images', ['id' => $image->id]);
590 public function test_image_delete_all_permission()
592 $this->giveUserPermissions($this->user, ['image-update-all']);
593 $admin = $this->getAdmin();
594 $page = \BookStack\Page::first();
595 $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
597 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
598 ->seeStatusCode(403);
600 $this->giveUserPermissions($this->user, ['image-delete-own']);
602 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
603 ->seeStatusCode(403);
605 $this->giveUserPermissions($this->user, ['image-delete-all']);
607 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
609 ->dontSeeInDatabase('images', ['id' => $image->id]);
612 public function test_role_permission_removal()
614 // To cover issue fixed in f99c8ff99aee9beb8c692f36d4b84dc6e651e50a.
615 $page = Page::first();
616 $viewerRole = \BookStack\Role::getRole('viewer');
617 $viewer = $this->getViewer();
618 $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(200);
620 $this->asAdmin()->put('/settings/roles/' . $viewerRole->id, [
621 'display_name' => $viewerRole->display_name,
622 'description' => $viewerRole->description,
624 ])->assertResponseStatus(302);
626 $this->expectException(HttpException::class);
627 $this->actingAs($viewer)->visit($page->getUrl())->assertResponseStatus(404);
630 public function test_empty_state_actions_not_visible_without_permission()
632 $admin = $this->getAdmin();
634 $book = factory(\BookStack\Book::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id]);
635 $this->updateEntityPermissions($book);
636 $this->actingAs($this->getViewer())->visit($book->getUrl())
637 ->dontSee('Create a new page')
638 ->dontSee('Add a chapter');
641 $chapter = factory(\BookStack\Chapter::class)->create(['created_by' => $admin->id, 'updated_by' => $admin->id, 'book_id' => $book->id]);
642 $this->updateEntityPermissions($chapter);
643 $this->actingAs($this->getViewer())->visit($chapter->getUrl())
644 ->dontSee('Create a new page')
645 ->dontSee('Sort the current book');
648 public function test_comment_create_permission () {
649 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
651 $this->actingAs($this->user)->addComment($ownPage);
653 $this->assertResponseStatus(403);
655 $this->giveUserPermissions($this->user, ['comment-create-all']);
657 $this->actingAs($this->user)->addComment($ownPage);
658 $this->assertResponseStatus(200);
662 public function test_comment_update_own_permission () {
663 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
664 $this->giveUserPermissions($this->user, ['comment-create-all']);
665 $commentId = $this->actingAs($this->user)->addComment($ownPage);
667 // no comment-update-own
668 $this->actingAs($this->user)->updateComment($commentId);
669 $this->assertResponseStatus(403);
671 $this->giveUserPermissions($this->user, ['comment-update-own']);
673 // now has comment-update-own
674 $this->actingAs($this->user)->updateComment($commentId);
675 $this->assertResponseStatus(200);
678 public function test_comment_update_all_permission () {
679 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
680 $commentId = $this->asAdmin()->addComment($ownPage);
682 // no comment-update-all
683 $this->actingAs($this->user)->updateComment($commentId);
684 $this->assertResponseStatus(403);
686 $this->giveUserPermissions($this->user, ['comment-update-all']);
688 // now has comment-update-all
689 $this->actingAs($this->user)->updateComment($commentId);
690 $this->assertResponseStatus(200);
693 public function test_comment_delete_own_permission () {
694 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
695 $this->giveUserPermissions($this->user, ['comment-create-all']);
696 $commentId = $this->actingAs($this->user)->addComment($ownPage);
698 // no comment-delete-own
699 $this->actingAs($this->user)->deleteComment($commentId);
700 $this->assertResponseStatus(403);
702 $this->giveUserPermissions($this->user, ['comment-delete-own']);
704 // now has comment-update-own
705 $this->actingAs($this->user)->deleteComment($commentId);
706 $this->assertResponseStatus(200);
709 public function test_comment_delete_all_permission () {
710 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
711 $commentId = $this->asAdmin()->addComment($ownPage);
713 // no comment-delete-all
714 $this->actingAs($this->user)->deleteComment($commentId);
715 $this->assertResponseStatus(403);
717 $this->giveUserPermissions($this->user, ['comment-delete-all']);
719 // now has comment-delete-all
720 $this->actingAs($this->user)->deleteComment($commentId);
721 $this->assertResponseStatus(200);
724 private function addComment($page) {
725 $comment = factory(\BookStack\Comment::class)->make();
726 $url = "/ajax/page/$page->id/comment";
728 'text' => $comment->text,
729 'html' => $comment->html
732 $this->postJson($url, $request);
733 $comment = $page->comments()->first();
734 return $comment === null ? null : $comment->id;
737 private function updateComment($commentId) {
738 $comment = factory(\BookStack\Comment::class)->make();
739 $url = "/ajax/comment/$commentId";
741 'text' => $comment->text,
742 'html' => $comment->html
745 return $this->putJson($url, $request);
748 private function deleteComment($commentId) {
749 $url = '/ajax/comment/' . $commentId;
750 return $this->json('DELETE', $url);