3 namespace Tests\Permissions;
5 use BookStack\Auth\User;
6 use BookStack\Entities\Models\Book;
7 use BookStack\Entities\Models\Bookshelf;
8 use BookStack\Entities\Models\Chapter;
9 use BookStack\Entities\Models\Entity;
10 use BookStack\Entities\Models\Page;
11 use Illuminate\Support\Str;
14 class EntityPermissionsTest extends TestCase
26 protected function setUp(): void
29 $this->user = $this->getEditor();
30 $this->viewer = $this->getViewer();
33 protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
36 $this->user->roles->first(),
37 $this->viewer->roles->first(),
39 $this->setEntityRestrictions($entity, $actions, $roles);
42 public function test_bookshelf_view_restriction()
44 /** @var Bookshelf $shelf */
45 $shelf = Bookshelf::query()->first();
47 $this->actingAs($this->user)
48 ->get($shelf->getUrl())
51 $this->setRestrictionsForTestRoles($shelf, []);
53 $this->followingRedirects()->get($shelf->getUrl())
54 ->assertSee('Bookshelf not found');
56 $this->setRestrictionsForTestRoles($shelf, ['view']);
58 $this->get($shelf->getUrl())
59 ->assertSee($shelf->name);
62 public function test_bookshelf_update_restriction()
64 /** @var Bookshelf $shelf */
65 $shelf = Bookshelf::query()->first();
67 $this->actingAs($this->user)
68 ->get($shelf->getUrl('/edit'))
69 ->assertSee('Edit Book');
71 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
73 $resp = $this->get($shelf->getUrl('/edit'))
74 ->assertRedirect('/');
75 $this->followRedirects($resp)->assertSee('You do not have permission');
77 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
79 $this->get($shelf->getUrl('/edit'))
83 public function test_bookshelf_delete_restriction()
85 /** @var Bookshelf $shelf */
86 $shelf = Bookshelf::query()->first();
88 $this->actingAs($this->user)
89 ->get($shelf->getUrl('/delete'))
90 ->assertSee('Delete Book');
92 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
94 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
95 $this->get('/')->assertSee('You do not have permission');
97 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
99 $this->get($shelf->getUrl('/delete'))
101 ->assertSee('Delete Book');
104 public function test_book_view_restriction()
106 /** @var Book $book */
107 $book = Book::query()->first();
108 $bookPage = $book->pages->first();
109 $bookChapter = $book->chapters->first();
111 $bookUrl = $book->getUrl();
112 $this->actingAs($this->user)
116 $this->setRestrictionsForTestRoles($book, []);
118 $this->followingRedirects()->get($bookUrl)
119 ->assertSee('Book not found');
120 $this->followingRedirects()->get($bookPage->getUrl())
121 ->assertSee('Page not found');
122 $this->followingRedirects()->get($bookChapter->getUrl())
123 ->assertSee('Chapter not found');
125 $this->setRestrictionsForTestRoles($book, ['view']);
128 ->assertSee($book->name);
129 $this->get($bookPage->getUrl())
130 ->assertSee($bookPage->name);
131 $this->get($bookChapter->getUrl())
132 ->assertSee($bookChapter->name);
135 public function test_book_create_restriction()
137 /** @var Book $book */
138 $book = Book::query()->first();
140 $bookUrl = $book->getUrl();
141 $resp = $this->actingAs($this->viewer)->get($bookUrl);
142 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
143 ->assertElementNotContains('.actions', 'New Chapter');
144 $resp = $this->actingAs($this->user)->get($bookUrl);
145 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
146 ->assertElementContains('.actions', 'New Chapter');
148 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
150 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
151 $this->get('/')->assertSee('You do not have permission');
153 $this->get($bookUrl . '/create-page')->assertRedirect('/');
154 $this->get('/')->assertSee('You do not have permission');
156 $resp = $this->get($bookUrl);
157 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
158 ->assertElementNotContains('.actions', 'New Chapter');
160 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
162 $resp = $this->post($book->getUrl('/create-chapter'), [
163 'name' => 'test chapter',
164 'description' => 'desc',
166 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
168 $this->get($book->getUrl('/create-page'));
169 /** @var Page $page */
170 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
171 $resp = $this->post($page->getUrl(), [
172 'name' => 'test page',
173 'html' => 'test content',
175 $resp->assertRedirect($book->getUrl('/page/test-page'));
177 $resp = $this->get($bookUrl);
178 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
179 ->assertElementContains('.actions', 'New Chapter');
182 public function test_book_update_restriction()
184 /** @var Book $book */
185 $book = Book::query()->first();
186 $bookPage = $book->pages->first();
187 $bookChapter = $book->chapters->first();
189 $bookUrl = $book->getUrl();
190 $this->actingAs($this->user)
191 ->get($bookUrl . '/edit')
192 ->assertSee('Edit Book');
194 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
196 $this->get($bookUrl . '/edit')->assertRedirect('/');
197 $this->get('/')->assertSee('You do not have permission');
198 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
199 $this->get('/')->assertSee('You do not have permission');
200 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
201 $this->get('/')->assertSee('You do not have permission');
203 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
205 $this->get($bookUrl . '/edit')->assertOk();
206 $this->get($bookPage->getUrl() . '/edit')->assertOk();
207 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
210 public function test_book_delete_restriction()
212 /** @var Book $book */
213 $book = Book::query()->first();
214 $bookPage = $book->pages->first();
215 $bookChapter = $book->chapters->first();
217 $bookUrl = $book->getUrl();
218 $this->actingAs($this->user)->get($bookUrl . '/delete')
219 ->assertSee('Delete Book');
221 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
223 $this->get($bookUrl . '/delete')->assertRedirect('/');
224 $this->get('/')->assertSee('You do not have permission');
225 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
226 $this->get('/')->assertSee('You do not have permission');
227 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
228 $this->get('/')->assertSee('You do not have permission');
230 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
232 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
233 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
234 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
237 public function test_chapter_view_restriction()
239 /** @var Chapter $chapter */
240 $chapter = Chapter::query()->first();
241 $chapterPage = $chapter->pages->first();
243 $chapterUrl = $chapter->getUrl();
244 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
246 $this->setRestrictionsForTestRoles($chapter, []);
248 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
249 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
251 $this->setRestrictionsForTestRoles($chapter, ['view']);
253 $this->get($chapterUrl)->assertSee($chapter->name);
254 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
257 public function test_chapter_create_restriction()
259 /** @var Chapter $chapter */
260 $chapter = Chapter::query()->first();
262 $chapterUrl = $chapter->getUrl();
263 $resp = $this->actingAs($this->user)->get($chapterUrl);
264 $this->withHtml($resp)->assertElementContains('.actions', 'New Page');
266 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
268 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
269 $this->get('/')->assertSee('You do not have permission');
270 $this->withHtml($this->get($chapterUrl))->assertElementNotContains('.actions', 'New Page');
272 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
274 $this->get($chapter->getUrl('/create-page'));
275 /** @var Page $page */
276 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
277 $resp = $this->post($page->getUrl(), [
278 'name' => 'test page',
279 'html' => 'test content',
281 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
283 $this->withHtml($this->get($chapterUrl))->assertElementContains('.actions', 'New Page');
286 public function test_chapter_update_restriction()
288 /** @var Chapter $chapter */
289 $chapter = Chapter::query()->first();
290 $chapterPage = $chapter->pages->first();
292 $chapterUrl = $chapter->getUrl();
293 $this->actingAs($this->user)->get($chapterUrl . '/edit')
294 ->assertSee('Edit Chapter');
296 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
298 $this->get($chapterUrl . '/edit')->assertRedirect('/');
299 $this->get('/')->assertSee('You do not have permission');
300 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
301 $this->get('/')->assertSee('You do not have permission');
303 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
305 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
306 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
309 public function test_chapter_delete_restriction()
311 /** @var Chapter $chapter */
312 $chapter = Chapter::query()->first();
313 $chapterPage = $chapter->pages->first();
315 $chapterUrl = $chapter->getUrl();
316 $this->actingAs($this->user)
317 ->get($chapterUrl . '/delete')
318 ->assertSee('Delete Chapter');
320 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
322 $this->get($chapterUrl . '/delete')->assertRedirect('/');
323 $this->get('/')->assertSee('You do not have permission');
324 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
325 $this->get('/')->assertSee('You do not have permission');
327 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
329 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
330 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
333 public function test_page_view_restriction()
335 /** @var Page $page */
336 $page = Page::query()->first();
338 $pageUrl = $page->getUrl();
339 $this->actingAs($this->user)->get($pageUrl)->assertOk();
341 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
343 $this->get($pageUrl)->assertSee('Page not found');
345 $this->setRestrictionsForTestRoles($page, ['view']);
347 $this->get($pageUrl)->assertSee($page->name);
350 public function test_page_update_restriction()
352 /** @var Page $page */
353 $page = Page::query()->first();
355 $pageUrl = $page->getUrl();
356 $resp = $this->actingAs($this->user)
357 ->get($pageUrl . '/edit');
358 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
360 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
362 $this->get($pageUrl . '/edit')->assertRedirect('/');
363 $this->get('/')->assertSee('You do not have permission');
365 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
367 $resp = $this->get($pageUrl . '/edit')
369 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
372 public function test_page_delete_restriction()
374 /** @var Page $page */
375 $page = Page::query()->first();
377 $pageUrl = $page->getUrl();
378 $this->actingAs($this->user)
379 ->get($pageUrl . '/delete')
380 ->assertSee('Delete Page');
382 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
384 $this->get($pageUrl . '/delete')->assertRedirect('/');
385 $this->get('/')->assertSee('You do not have permission');
387 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
389 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
392 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
394 /** @var Entity $modelInstance */
395 $modelInstance = $model::query()->first();
396 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
399 $this->put($modelInstance->getUrl('/permissions'), [
400 'restricted' => 'true',
403 $permission => 'true',
408 $this->assertDatabaseHas($modelInstance->getTable(), ['id' => $modelInstance->id, 'restricted' => true]);
409 $this->assertDatabaseHas('entity_permissions', [
410 'restrictable_id' => $modelInstance->id,
411 'restrictable_type' => $modelInstance->getMorphClass(),
412 'role_id' => $roleId,
413 'action' => $permission,
417 public function test_bookshelf_restriction_form()
419 $this->entityRestrictionFormTest(Bookshelf::class, 'Bookshelf Permissions', 'view', '2');
422 public function test_book_restriction_form()
424 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
427 public function test_chapter_restriction_form()
429 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
432 public function test_page_restriction_form()
434 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
437 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
439 /** @var Chapter $chapter */
440 $chapter = Chapter::query()->first();
441 $page = $chapter->pages->first();
442 $page2 = $chapter->pages[2];
444 $this->setRestrictionsForTestRoles($page, []);
446 $resp = $this->actingAs($this->user)->get($page2->getUrl());
447 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
450 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
452 /** @var Chapter $chapter */
453 $chapter = Chapter::query()->first();
454 $page = $chapter->pages->first();
456 $this->setRestrictionsForTestRoles($page, []);
458 $resp = $this->actingAs($this->user)->get($chapter->getUrl());
459 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
462 public function test_restricted_pages_not_visible_on_chapter_pages()
464 /** @var Chapter $chapter */
465 $chapter = Chapter::query()->first();
466 $page = $chapter->pages->first();
468 $this->setRestrictionsForTestRoles($page, []);
470 $this->actingAs($this->user)
471 ->get($chapter->getUrl())
472 ->assertDontSee($page->name);
475 public function test_restricted_chapter_pages_not_visible_on_book_page()
477 /** @var Chapter $chapter */
478 $chapter = Chapter::query()->first();
479 $this->actingAs($this->user)
480 ->get($chapter->book->getUrl())
481 ->assertSee($chapter->pages->first()->name);
483 foreach ($chapter->pages as $page) {
484 $this->setRestrictionsForTestRoles($page, []);
487 $this->actingAs($this->user)
488 ->get($chapter->book->getUrl())
489 ->assertDontSee($chapter->pages->first()->name);
492 public function test_bookshelf_update_restriction_override()
494 /** @var Bookshelf $shelf */
495 $shelf = Bookshelf::query()->first();
497 $this->actingAs($this->viewer)
498 ->get($shelf->getUrl('/edit'))
499 ->assertDontSee('Edit Book');
501 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
503 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
504 $this->get('/')->assertSee('You do not have permission');
506 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
508 $this->get($shelf->getUrl('/edit'))->assertOk();
511 public function test_bookshelf_delete_restriction_override()
513 /** @var Bookshelf $shelf */
514 $shelf = Bookshelf::query()->first();
516 $this->actingAs($this->viewer)
517 ->get($shelf->getUrl('/delete'))
518 ->assertDontSee('Delete Book');
520 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
522 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
523 $this->get('/')->assertSee('You do not have permission');
525 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
527 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Book');
530 public function test_book_create_restriction_override()
532 /** @var Book $book */
533 $book = Book::query()->first();
535 $bookUrl = $book->getUrl();
536 $resp = $this->actingAs($this->viewer)->get($bookUrl);
537 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
538 ->assertElementNotContains('.actions', 'New Chapter');
540 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
542 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
543 $this->get('/')->assertSee('You do not have permission');
544 $this->get($bookUrl . '/create-page')->assertRedirect('/');
545 $this->get('/')->assertSee('You do not have permission');
546 $resp = $this->get($bookUrl);
547 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
548 ->assertElementNotContains('.actions', 'New Chapter');
550 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
552 $resp = $this->post($book->getUrl('/create-chapter'), [
553 'name' => 'test chapter',
554 'description' => 'test desc',
556 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
558 $this->get($book->getUrl('/create-page'));
559 /** @var Page $page */
560 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
561 $resp = $this->post($page->getUrl(), [
562 'name' => 'test page',
563 'html' => 'test desc',
565 $resp->assertRedirect($book->getUrl('/page/test-page'));
567 $resp = $this->get($bookUrl);
568 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
569 ->assertElementContains('.actions', 'New Chapter');
572 public function test_book_update_restriction_override()
574 /** @var Book $book */
575 $book = Book::query()->first();
576 $bookPage = $book->pages->first();
577 $bookChapter = $book->chapters->first();
579 $bookUrl = $book->getUrl();
580 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
581 ->assertDontSee('Edit Book');
583 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
585 $this->get($bookUrl . '/edit')->assertRedirect('/');
586 $this->get('/')->assertSee('You do not have permission');
587 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
588 $this->get('/')->assertSee('You do not have permission');
589 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
590 $this->get('/')->assertSee('You do not have permission');
592 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
594 $this->get($bookUrl . '/edit')->assertOk();
595 $this->get($bookPage->getUrl() . '/edit')->assertOk();
596 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
599 public function test_book_delete_restriction_override()
601 /** @var Book $book */
602 $book = Book::query()->first();
603 $bookPage = $book->pages->first();
604 $bookChapter = $book->chapters->first();
606 $bookUrl = $book->getUrl();
607 $this->actingAs($this->viewer)
608 ->get($bookUrl . '/delete')
609 ->assertDontSee('Delete Book');
611 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
613 $this->get($bookUrl . '/delete')->assertRedirect('/');
614 $this->get('/')->assertSee('You do not have permission');
615 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
616 $this->get('/')->assertSee('You do not have permission');
617 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
618 $this->get('/')->assertSee('You do not have permission');
620 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
622 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
623 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
624 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
627 public function test_page_visible_if_has_permissions_when_book_not_visible()
629 /** @var Book $book */
630 $book = Book::query()->first();
631 $bookChapter = $book->chapters->first();
632 $bookPage = $bookChapter->pages->first();
634 foreach ([$book, $bookChapter, $bookPage] as $entity) {
635 $entity->name = Str::random(24);
639 $this->setRestrictionsForTestRoles($book, []);
640 $this->setRestrictionsForTestRoles($bookPage, ['view']);
642 $this->actingAs($this->viewer);
643 $resp = $this->get($bookPage->getUrl());
645 $resp->assertSee($bookPage->name);
646 $resp->assertDontSee(substr($book->name, 0, 15));
647 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
650 public function test_book_sort_view_permission()
652 /** @var Book $firstBook */
653 $firstBook = Book::query()->first();
654 /** @var Book $secondBook */
655 $secondBook = Book::query()->find(2);
657 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
658 $this->setRestrictionsForTestRoles($secondBook, ['view']);
660 // Test sort page visibility
661 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
662 $this->get('/')->assertSee('You do not have permission');
664 // Check sort page on first book
665 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
668 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
670 /** @var Book $book */
671 $book = Book::query()->first();
672 $this->setRestrictionsForTestRoles($book, []);
673 $bookChapter = $book->chapters->first();
674 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
676 $this->actingAs($this->user)->get($bookChapter->getUrl())
677 ->assertDontSee('New Page');
679 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
681 $this->get($bookChapter->getUrl('/create-page'));
682 /** @var Page $page */
683 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
684 $resp = $this->post($page->getUrl(), [
685 'name' => 'test page',
686 'html' => 'test content',
688 $resp->assertRedirect($book->getUrl('/page/test-page'));