3 use BookStack\Repos\PermissionsRepo;
6 class RolesTest extends BrowserKitTest
10 public function setUp()
13 $this->user = $this->getViewer();
16 protected function getViewer()
18 $role = \BookStack\Role::getRole('viewer');
19 $viewer = $this->getNewBlankUser();
20 $viewer->attachRole($role);;
25 * Give the given user some permissions.
26 * @param \BookStack\User $user
27 * @param array $permissions
29 protected function giveUserPermissions(\BookStack\User $user, $permissions = [])
31 $newRole = $this->createNewRole($permissions);
32 $user->attachRole($newRole);
34 $user->permissions(false);
38 * Create a new basic role for testing purposes.
39 * @param array $permissions
42 protected function createNewRole($permissions = [])
44 $permissionRepo = app(PermissionsRepo::class);
45 $roleData = factory(\BookStack\Role::class)->make()->toArray();
46 $roleData['permissions'] = array_flip($permissions);
47 return $permissionRepo->saveNewRole($roleData);
50 public function test_admin_can_see_settings()
52 $this->asAdmin()->visit('/settings')->see('Settings');
55 public function test_cannot_delete_admin_role()
57 $adminRole = \BookStack\Role::getRole('admin');
58 $deletePageUrl = '/settings/roles/delete/' . $adminRole->id;
59 $this->asAdmin()->visit($deletePageUrl)
61 ->seePageIs($deletePageUrl)
62 ->see('cannot be deleted');
65 public function test_role_cannot_be_deleted_if_default()
67 $newRole = $this->createNewRole();
68 $this->setSettings(['registration-role' => $newRole->id]);
70 $deletePageUrl = '/settings/roles/delete/' . $newRole->id;
71 $this->asAdmin()->visit($deletePageUrl)
73 ->seePageIs($deletePageUrl)
74 ->see('cannot be deleted');
77 public function test_role_create_update_delete_flow()
79 $testRoleName = 'Test Role';
80 $testRoleDesc = 'a little test description';
81 $testRoleUpdateName = 'An Super Updated role';
84 $this->asAdmin()->visit('/settings')
86 ->seePageIs('/settings/roles')
87 ->click('Create New Role')
88 ->type('Test Role', 'display_name')
89 ->type('A little test description', 'description')
91 ->seeInDatabase('roles', ['display_name' => $testRoleName, 'name' => 'test-role', 'description' => $testRoleDesc])
92 ->seePageIs('/settings/roles');
94 $this->asAdmin()->visit('/settings/roles')
96 ->click($testRoleName)
97 ->type($testRoleUpdateName, '#display_name')
99 ->seeInDatabase('roles', ['display_name' => $testRoleUpdateName, 'name' => 'test-role', 'description' => $testRoleDesc])
100 ->seePageIs('/settings/roles');
102 $this->asAdmin()->visit('/settings/roles')
103 ->click($testRoleUpdateName)
104 ->click('Delete Role')
105 ->see($testRoleUpdateName)
107 ->seePageIs('/settings/roles')
108 ->dontSee($testRoleUpdateName);
111 public function test_manage_user_permission()
113 $this->actingAs($this->user)->visit('/settings/users')
115 $this->giveUserPermissions($this->user, ['users-manage']);
116 $this->actingAs($this->user)->visit('/settings/users')
117 ->seePageIs('/settings/users');
120 public function test_user_roles_manage_permission()
122 $this->actingAs($this->user)->visit('/settings/roles')
123 ->seePageIs('/')->visit('/settings/roles/1')->seePageIs('/');
124 $this->giveUserPermissions($this->user, ['user-roles-manage']);
125 $this->actingAs($this->user)->visit('/settings/roles')
126 ->seePageIs('/settings/roles')->click('Admin')
130 public function test_settings_manage_permission()
132 $this->actingAs($this->user)->visit('/settings')
134 $this->giveUserPermissions($this->user, ['settings-manage']);
135 $this->actingAs($this->user)->visit('/settings')
136 ->seePageIs('/settings')->press('Save Settings')->see('Settings Saved');
139 public function test_restrictions_manage_all_permission()
141 $page = \BookStack\Page::take(1)->get()->first();
142 $this->actingAs($this->user)->visit($page->getUrl())
143 ->dontSee('Permissions')
144 ->visit($page->getUrl() . '/permissions')
146 $this->giveUserPermissions($this->user, ['restrictions-manage-all']);
147 $this->actingAs($this->user)->visit($page->getUrl())
149 ->click('Permissions')
150 ->see('Page Permissions')->seePageIs($page->getUrl() . '/permissions');
153 public function test_restrictions_manage_own_permission()
155 $otherUsersPage = \BookStack\Page::first();
156 $content = $this->createEntityChainBelongingToUser($this->user);
157 // Check can't restrict other's content
158 $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
159 ->dontSee('Permissions')
160 ->visit($otherUsersPage->getUrl() . '/permissions')
162 // Check can't restrict own content
163 $this->actingAs($this->user)->visit($content['page']->getUrl())
164 ->dontSee('Permissions')
165 ->visit($content['page']->getUrl() . '/permissions')
168 $this->giveUserPermissions($this->user, ['restrictions-manage-own']);
170 // Check can't restrict other's content
171 $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
172 ->dontSee('Permissions')
173 ->visit($otherUsersPage->getUrl() . '/permissions')
175 // Check can restrict own content
176 $this->actingAs($this->user)->visit($content['page']->getUrl())
178 ->click('Permissions')
179 ->seePageIs($content['page']->getUrl() . '/permissions');
183 * Check a standard entity access permission
184 * @param string $permission
185 * @param array $accessUrls Urls that are only accessible after having the permission
186 * @param array $visibles Check this text, In the buttons toolbar, is only visible with the permission
188 private function checkAccessPermission($permission, $accessUrls = [], $visibles = [])
190 foreach ($accessUrls as $url) {
191 $this->actingAs($this->user)->visit($url)
194 foreach ($visibles as $url => $text) {
195 $this->actingAs($this->user)->visit($url)
196 ->dontSeeInElement('.action-buttons',$text);
199 $this->giveUserPermissions($this->user, [$permission]);
201 foreach ($accessUrls as $url) {
202 $this->actingAs($this->user)->visit($url)
205 foreach ($visibles as $url => $text) {
206 $this->actingAs($this->user)->visit($url)
211 public function test_books_create_all_permissions()
213 $this->checkAccessPermission('book-create-all', [
216 '/books' => 'Create New Book'
219 $this->visit('/books/create')
220 ->type('test book', 'name')
221 ->type('book desc', 'description')
223 ->seePageIs('/books/test-book');
226 public function test_books_edit_own_permission()
228 $otherBook = \BookStack\Book::take(1)->get()->first();
229 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
230 $this->checkAccessPermission('book-update-own', [
231 $ownBook->getUrl() . '/edit'
233 $ownBook->getUrl() => 'Edit'
236 $this->visit($otherBook->getUrl())
237 ->dontSeeInElement('.action-buttons', 'Edit')
238 ->visit($otherBook->getUrl() . '/edit')
242 public function test_books_edit_all_permission()
244 $otherBook = \BookStack\Book::take(1)->get()->first();
245 $this->checkAccessPermission('book-update-all', [
246 $otherBook->getUrl() . '/edit'
248 $otherBook->getUrl() => 'Edit'
252 public function test_books_delete_own_permission()
254 $this->giveUserPermissions($this->user, ['book-update-all']);
255 $otherBook = \BookStack\Book::take(1)->get()->first();
256 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
257 $this->checkAccessPermission('book-delete-own', [
258 $ownBook->getUrl() . '/delete'
260 $ownBook->getUrl() => 'Delete'
263 $this->visit($otherBook->getUrl())
264 ->dontSeeInElement('.action-buttons', 'Delete')
265 ->visit($otherBook->getUrl() . '/delete')
267 $this->visit($ownBook->getUrl())->visit($ownBook->getUrl() . '/delete')
269 ->seePageIs('/books')
270 ->dontSee($ownBook->name);
273 public function test_books_delete_all_permission()
275 $this->giveUserPermissions($this->user, ['book-update-all']);
276 $otherBook = \BookStack\Book::take(1)->get()->first();
277 $this->checkAccessPermission('book-delete-all', [
278 $otherBook->getUrl() . '/delete'
280 $otherBook->getUrl() => 'Delete'
283 $this->visit($otherBook->getUrl())->visit($otherBook->getUrl() . '/delete')
285 ->seePageIs('/books')
286 ->dontSee($otherBook->name);
289 public function test_chapter_create_own_permissions()
291 $book = \BookStack\Book::take(1)->get()->first();
292 $ownBook = $this->createEntityChainBelongingToUser($this->user)['book'];
293 $baseUrl = $ownBook->getUrl() . '/chapter';
294 $this->checkAccessPermission('chapter-create-own', [
297 $ownBook->getUrl() => 'New Chapter'
300 $this->visit($baseUrl . '/create')
301 ->type('test chapter', 'name')
302 ->type('chapter desc', 'description')
303 ->press('Save Chapter')
304 ->seePageIs($baseUrl . '/test-chapter');
306 $this->visit($book->getUrl())
307 ->dontSeeInElement('.action-buttons', 'New Chapter')
308 ->visit($book->getUrl() . '/chapter/create')
312 public function test_chapter_create_all_permissions()
314 $book = \BookStack\Book::take(1)->get()->first();
315 $baseUrl = $book->getUrl() . '/chapter';
316 $this->checkAccessPermission('chapter-create-all', [
319 $book->getUrl() => 'New Chapter'
322 $this->visit($baseUrl . '/create')
323 ->type('test chapter', 'name')
324 ->type('chapter desc', 'description')
325 ->press('Save Chapter')
326 ->seePageIs($baseUrl . '/test-chapter');
329 public function test_chapter_edit_own_permission()
331 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
332 $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
333 $this->checkAccessPermission('chapter-update-own', [
334 $ownChapter->getUrl() . '/edit'
336 $ownChapter->getUrl() => 'Edit'
339 $this->visit($otherChapter->getUrl())
340 ->dontSeeInElement('.action-buttons', 'Edit')
341 ->visit($otherChapter->getUrl() . '/edit')
345 public function test_chapter_edit_all_permission()
347 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
348 $this->checkAccessPermission('chapter-update-all', [
349 $otherChapter->getUrl() . '/edit'
351 $otherChapter->getUrl() => 'Edit'
355 public function test_chapter_delete_own_permission()
357 $this->giveUserPermissions($this->user, ['chapter-update-all']);
358 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
359 $ownChapter = $this->createEntityChainBelongingToUser($this->user)['chapter'];
360 $this->checkAccessPermission('chapter-delete-own', [
361 $ownChapter->getUrl() . '/delete'
363 $ownChapter->getUrl() => 'Delete'
366 $bookUrl = $ownChapter->book->getUrl();
367 $this->visit($otherChapter->getUrl())
368 ->dontSeeInElement('.action-buttons', 'Delete')
369 ->visit($otherChapter->getUrl() . '/delete')
371 $this->visit($ownChapter->getUrl())->visit($ownChapter->getUrl() . '/delete')
373 ->seePageIs($bookUrl)
374 ->dontSeeInElement('.book-content', $ownChapter->name);
377 public function test_chapter_delete_all_permission()
379 $this->giveUserPermissions($this->user, ['chapter-update-all']);
380 $otherChapter = \BookStack\Chapter::take(1)->get()->first();
381 $this->checkAccessPermission('chapter-delete-all', [
382 $otherChapter->getUrl() . '/delete'
384 $otherChapter->getUrl() => 'Delete'
387 $bookUrl = $otherChapter->book->getUrl();
388 $this->visit($otherChapter->getUrl())->visit($otherChapter->getUrl() . '/delete')
390 ->seePageIs($bookUrl)
391 ->dontSeeInElement('.book-content', $otherChapter->name);
394 public function test_page_create_own_permissions()
396 $book = \BookStack\Book::first();
397 $chapter = \BookStack\Chapter::first();
399 $entities = $this->createEntityChainBelongingToUser($this->user);
400 $ownBook = $entities['book'];
401 $ownChapter = $entities['chapter'];
403 $baseUrl = $ownBook->getUrl() . '/page';
405 $createUrl = $baseUrl . '/create';
406 $createUrlChapter = $ownChapter->getUrl() . '/create-page';
407 $accessUrls = [$createUrl, $createUrlChapter];
409 foreach ($accessUrls as $url) {
410 $this->actingAs($this->user)->visit($url)
414 $this->checkAccessPermission('page-create-own', [], [
415 $ownBook->getUrl() => 'New Page',
416 $ownChapter->getUrl() => 'New Page'
419 $this->giveUserPermissions($this->user, ['page-create-own']);
421 foreach ($accessUrls as $index => $url) {
422 $this->actingAs($this->user)->visit($url);
423 $expectedUrl = \BookStack\Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
424 $this->seePageIs($expectedUrl);
427 $this->visit($baseUrl . '/create')
428 ->type('test page', 'name')
429 ->type('page desc', 'html')
431 ->seePageIs($baseUrl . '/test-page');
433 $this->visit($book->getUrl())
434 ->dontSeeInElement('.action-buttons', 'New Page')
435 ->visit($book->getUrl() . '/page/create')
437 $this->visit($chapter->getUrl())
438 ->dontSeeInElement('.action-buttons', 'New Page')
439 ->visit($chapter->getUrl() . '/create-page')
443 public function test_page_create_all_permissions()
445 $book = \BookStack\Book::take(1)->get()->first();
446 $chapter = \BookStack\Chapter::take(1)->get()->first();
447 $baseUrl = $book->getUrl() . '/page';
448 $createUrl = $baseUrl . '/create';
450 $createUrlChapter = $chapter->getUrl() . '/create-page';
451 $accessUrls = [$createUrl, $createUrlChapter];
453 foreach ($accessUrls as $url) {
454 $this->actingAs($this->user)->visit($url)
458 $this->checkAccessPermission('page-create-all', [], [
459 $book->getUrl() => 'New Page',
460 $chapter->getUrl() => 'New Page'
463 $this->giveUserPermissions($this->user, ['page-create-all']);
465 foreach ($accessUrls as $index => $url) {
466 $this->actingAs($this->user)->visit($url);
467 $expectedUrl = \BookStack\Page::where('draft', '=', true)->orderBy('id', 'desc')->first()->getUrl();
468 $this->seePageIs($expectedUrl);
471 $this->visit($baseUrl . '/create')
472 ->type('test page', 'name')
473 ->type('page desc', 'html')
475 ->seePageIs($baseUrl . '/test-page');
477 $this->visit($chapter->getUrl() . '/create-page')
478 ->type('new test page', 'name')
479 ->type('page desc', 'html')
481 ->seePageIs($baseUrl . '/new-test-page');
484 public function test_page_edit_own_permission()
486 $otherPage = \BookStack\Page::take(1)->get()->first();
487 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
488 $this->checkAccessPermission('page-update-own', [
489 $ownPage->getUrl() . '/edit'
491 $ownPage->getUrl() => 'Edit'
494 $this->visit($otherPage->getUrl())
495 ->dontSeeInElement('.action-buttons', 'Edit')
496 ->visit($otherPage->getUrl() . '/edit')
500 public function test_page_edit_all_permission()
502 $otherPage = \BookStack\Page::take(1)->get()->first();
503 $this->checkAccessPermission('page-update-all', [
504 $otherPage->getUrl() . '/edit'
506 $otherPage->getUrl() => 'Edit'
510 public function test_page_delete_own_permission()
512 $this->giveUserPermissions($this->user, ['page-update-all']);
513 $otherPage = \BookStack\Page::take(1)->get()->first();
514 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
515 $this->checkAccessPermission('page-delete-own', [
516 $ownPage->getUrl() . '/delete'
518 $ownPage->getUrl() => 'Delete'
521 $bookUrl = $ownPage->book->getUrl();
522 $this->visit($otherPage->getUrl())
523 ->dontSeeInElement('.action-buttons', 'Delete')
524 ->visit($otherPage->getUrl() . '/delete')
526 $this->visit($ownPage->getUrl())->visit($ownPage->getUrl() . '/delete')
528 ->seePageIs($bookUrl)
529 ->dontSeeInElement('.book-content', $ownPage->name);
532 public function test_page_delete_all_permission()
534 $this->giveUserPermissions($this->user, ['page-update-all']);
535 $otherPage = \BookStack\Page::take(1)->get()->first();
536 $this->checkAccessPermission('page-delete-all', [
537 $otherPage->getUrl() . '/delete'
539 $otherPage->getUrl() => 'Delete'
542 $bookUrl = $otherPage->book->getUrl();
543 $this->visit($otherPage->getUrl())->visit($otherPage->getUrl() . '/delete')
545 ->seePageIs($bookUrl)
546 ->dontSeeInElement('.book-content', $otherPage->name);
549 public function test_public_role_visible_in_user_edit_screen()
551 $user = \BookStack\User::first();
552 $this->asAdmin()->visit('/settings/users/' . $user->id)
553 ->seeElement('#roles-admin')
554 ->seeElement('#roles-public');
557 public function test_public_role_visible_in_role_listing()
559 $this->asAdmin()->visit('/settings/roles')
564 public function test_public_role_visible_in_default_role_setting()
566 $this->asAdmin()->visit('/settings')
567 ->seeElement('[data-role-name="admin"]')
568 ->seeElement('[data-role-name="public"]');
572 public function test_public_role_not_deleteable()
574 $this->asAdmin()->visit('/settings/roles')
577 ->click('Delete Role')
580 ->see('Cannot be deleted');
585 public function test_image_delete_own_permission()
587 $this->giveUserPermissions($this->user, ['image-update-all']);
588 $page = \BookStack\Page::first();
589 $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $this->user->id, 'updated_by' => $this->user->id]);
591 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
592 ->seeStatusCode(403);
594 $this->giveUserPermissions($this->user, ['image-delete-own']);
596 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
598 ->dontSeeInDatabase('images', ['id' => $image->id]);
601 public function test_image_delete_all_permission()
603 $this->giveUserPermissions($this->user, ['image-update-all']);
604 $admin = $this->getAdmin();
605 $page = \BookStack\Page::first();
606 $image = factory(\BookStack\Image::class)->create(['uploaded_to' => $page->id, 'created_by' => $admin->id, 'updated_by' => $admin->id]);
608 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
609 ->seeStatusCode(403);
611 $this->giveUserPermissions($this->user, ['image-delete-own']);
613 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
614 ->seeStatusCode(403);
616 $this->giveUserPermissions($this->user, ['image-delete-all']);
618 $this->actingAs($this->user)->json('delete', '/images/' . $image->id)
620 ->dontSeeInDatabase('images', ['id' => $image->id]);
623 public function test_comment_create_permission () {
624 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
626 $this->actingAs($this->user)->addComment($ownPage);
628 $this->assertResponseStatus(403);
630 $this->giveUserPermissions($this->user, ['comment-create-all']);
632 $this->actingAs($this->user)->addComment($ownPage);
633 $this->assertResponseOk(200)->seeJsonContains(['status' => 'success']);
637 public function test_comment_update_own_permission () {
638 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
639 $this->giveUserPermissions($this->user, ['comment-create-all']);
640 $comment = $this->actingAs($this->user)->addComment($ownPage);
642 // no comment-update-own
643 $this->actingAs($this->user)->updateComment($ownPage, $comment['id']);
644 $this->assertResponseStatus(403);
646 $this->giveUserPermissions($this->user, ['comment-update-own']);
648 // now has comment-update-own
649 $this->actingAs($this->user)->updateComment($ownPage, $comment['id']);
650 $this->assertResponseOk()->seeJsonContains(['status' => 'success']);
653 public function test_comment_update_all_permission () {
654 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
655 $comment = $this->asAdmin()->addComment($ownPage);
657 // no comment-update-all
658 $this->actingAs($this->user)->updateComment($ownPage, $comment['id']);
659 $this->assertResponseStatus(403);
661 $this->giveUserPermissions($this->user, ['comment-update-all']);
663 // now has comment-update-all
664 $this->actingAs($this->user)->updateComment($ownPage, $comment['id']);
665 $this->assertResponseOk()->seeJsonContains(['status' => 'success']);
668 public function test_comment_delete_own_permission () {
669 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
670 $this->giveUserPermissions($this->user, ['comment-create-all']);
671 $comment = $this->actingAs($this->user)->addComment($ownPage);
673 // no comment-delete-own
674 $this->actingAs($this->user)->deleteComment($comment['id']);
675 $this->assertResponseStatus(403);
677 $this->giveUserPermissions($this->user, ['comment-delete-own']);
679 // now has comment-update-own
680 $this->actingAs($this->user)->deleteComment($comment['id']);
681 $this->assertResponseOk()->seeJsonContains(['status' => 'success']);
684 public function test_comment_delete_all_permission () {
685 $ownPage = $this->createEntityChainBelongingToUser($this->user)['page'];
686 $comment = $this->asAdmin()->addComment($ownPage);
688 // no comment-delete-all
689 $this->actingAs($this->user)->deleteComment($comment['id']);
690 $this->assertResponseStatus(403);
692 $this->giveUserPermissions($this->user, ['comment-delete-all']);
694 // now has comment-delete-all
695 $this->actingAs($this->user)->deleteComment($comment['id']);
696 $this->assertResponseOk()->seeJsonContains(['status' => 'success']);
699 private function addComment($page) {
700 $comment = factory(\BookStack\Comment::class)->make();
701 $url = "/ajax/page/$page->id/comment/";
703 'text' => $comment->text,
704 'html' => $comment->html
707 $this->json('POST', $url, $request);
708 $resp = $this->decodeResponseJson();
709 if (isset($resp['comment'])) {
710 return $resp['comment'];
715 private function updateComment($page, $commentId) {
716 $comment = factory(\BookStack\Comment::class)->make();
717 $url = "/ajax/page/$page->id/comment/$commentId";
719 'text' => $comment->text,
720 'html' => $comment->html
723 return $this->json('PUT', $url, $request);
726 private function deleteComment($commentId) {
727 $url = '/ajax/comment/' . $commentId;
728 return $this->json('DELETE', $url);