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
17 protected User $viewer;
19 protected function setUp(): void
22 $this->user = $this->getEditor();
23 $this->viewer = $this->getViewer();
26 protected function setRestrictionsForTestRoles(Entity $entity, array $actions = [])
29 $this->user->roles->first(),
30 $this->viewer->roles->first(),
32 $this->entities->setPermissions($entity, $actions, $roles);
35 public function test_bookshelf_view_restriction()
37 $shelf = $this->entities->shelf();
39 $this->actingAs($this->user)
40 ->get($shelf->getUrl())
43 $this->setRestrictionsForTestRoles($shelf, []);
45 $this->followingRedirects()->get($shelf->getUrl())
46 ->assertSee('Shelf not found');
48 $this->setRestrictionsForTestRoles($shelf, ['view']);
50 $this->get($shelf->getUrl())
51 ->assertSee($shelf->name);
54 public function test_bookshelf_update_restriction()
56 $shelf = $this->entities->shelf();
58 $this->actingAs($this->user)
59 ->get($shelf->getUrl('/edit'))
60 ->assertSee('Edit Shelf');
62 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
64 $resp = $this->get($shelf->getUrl('/edit'))
65 ->assertRedirect('/');
66 $this->followRedirects($resp)->assertSee('You do not have permission');
68 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
70 $this->get($shelf->getUrl('/edit'))
74 public function test_bookshelf_delete_restriction()
76 $shelf = $this->entities->shelf();
78 $this->actingAs($this->user)
79 ->get($shelf->getUrl('/delete'))
80 ->assertSee('Delete Shelf');
82 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
84 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
85 $this->get('/')->assertSee('You do not have permission');
87 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
89 $this->get($shelf->getUrl('/delete'))
91 ->assertSee('Delete Shelf');
94 public function test_book_view_restriction()
96 $book = $this->entities->book();
97 $bookPage = $book->pages->first();
98 $bookChapter = $book->chapters->first();
100 $bookUrl = $book->getUrl();
101 $this->actingAs($this->user)
105 $this->setRestrictionsForTestRoles($book, []);
107 $this->followingRedirects()->get($bookUrl)
108 ->assertSee('Book not found');
109 $this->followingRedirects()->get($bookPage->getUrl())
110 ->assertSee('Page not found');
111 $this->followingRedirects()->get($bookChapter->getUrl())
112 ->assertSee('Chapter not found');
114 $this->setRestrictionsForTestRoles($book, ['view']);
117 ->assertSee($book->name);
118 $this->get($bookPage->getUrl())
119 ->assertSee($bookPage->name);
120 $this->get($bookChapter->getUrl())
121 ->assertSee($bookChapter->name);
124 public function test_book_create_restriction()
126 $book = $this->entities->book();
128 $bookUrl = $book->getUrl();
129 $resp = $this->actingAs($this->viewer)->get($bookUrl);
130 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
131 ->assertElementNotContains('.actions', 'New Chapter');
132 $resp = $this->actingAs($this->user)->get($bookUrl);
133 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
134 ->assertElementContains('.actions', 'New Chapter');
136 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
138 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
139 $this->get('/')->assertSee('You do not have permission');
141 $this->get($bookUrl . '/create-page')->assertRedirect('/');
142 $this->get('/')->assertSee('You do not have permission');
144 $resp = $this->get($bookUrl);
145 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
146 ->assertElementNotContains('.actions', 'New Chapter');
148 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
150 $resp = $this->post($book->getUrl('/create-chapter'), [
151 'name' => 'test chapter',
152 'description' => 'desc',
154 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
156 $this->get($book->getUrl('/create-page'));
157 /** @var Page $page */
158 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
159 $resp = $this->post($page->getUrl(), [
160 'name' => 'test page',
161 'html' => 'test content',
163 $resp->assertRedirect($book->getUrl('/page/test-page'));
165 $resp = $this->get($bookUrl);
166 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
167 ->assertElementContains('.actions', 'New Chapter');
170 public function test_book_update_restriction()
172 $book = $this->entities->book();
173 $bookPage = $book->pages->first();
174 $bookChapter = $book->chapters->first();
176 $bookUrl = $book->getUrl();
177 $this->actingAs($this->user)
178 ->get($bookUrl . '/edit')
179 ->assertSee('Edit Book');
181 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
183 $this->get($bookUrl . '/edit')->assertRedirect('/');
184 $this->get('/')->assertSee('You do not have permission');
185 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
186 $this->get('/')->assertSee('You do not have permission');
187 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
188 $this->get('/')->assertSee('You do not have permission');
190 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
192 $this->get($bookUrl . '/edit')->assertOk();
193 $this->get($bookPage->getUrl() . '/edit')->assertOk();
194 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
197 public function test_book_delete_restriction()
199 $book = $this->entities->book();
200 $bookPage = $book->pages->first();
201 $bookChapter = $book->chapters->first();
203 $bookUrl = $book->getUrl();
204 $this->actingAs($this->user)->get($bookUrl . '/delete')
205 ->assertSee('Delete Book');
207 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
209 $this->get($bookUrl . '/delete')->assertRedirect('/');
210 $this->get('/')->assertSee('You do not have permission');
211 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
212 $this->get('/')->assertSee('You do not have permission');
213 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
214 $this->get('/')->assertSee('You do not have permission');
216 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
218 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
219 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
220 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
223 public function test_chapter_view_restriction()
225 $chapter = $this->entities->chapter();
226 $chapterPage = $chapter->pages->first();
228 $chapterUrl = $chapter->getUrl();
229 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
231 $this->setRestrictionsForTestRoles($chapter, []);
233 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
234 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
236 $this->setRestrictionsForTestRoles($chapter, ['view']);
238 $this->get($chapterUrl)->assertSee($chapter->name);
239 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
242 public function test_chapter_create_restriction()
244 $chapter = $this->entities->chapter();
246 $chapterUrl = $chapter->getUrl();
247 $resp = $this->actingAs($this->user)->get($chapterUrl);
248 $this->withHtml($resp)->assertElementContains('.actions', 'New Page');
250 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
252 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
253 $this->get('/')->assertSee('You do not have permission');
254 $this->withHtml($this->get($chapterUrl))->assertElementNotContains('.actions', 'New Page');
256 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
258 $this->get($chapter->getUrl('/create-page'));
259 /** @var Page $page */
260 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
261 $resp = $this->post($page->getUrl(), [
262 'name' => 'test page',
263 'html' => 'test content',
265 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
267 $this->withHtml($this->get($chapterUrl))->assertElementContains('.actions', 'New Page');
270 public function test_chapter_update_restriction()
272 $chapter = $this->entities->chapter();
273 $chapterPage = $chapter->pages->first();
275 $chapterUrl = $chapter->getUrl();
276 $this->actingAs($this->user)->get($chapterUrl . '/edit')
277 ->assertSee('Edit Chapter');
279 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
281 $this->get($chapterUrl . '/edit')->assertRedirect('/');
282 $this->get('/')->assertSee('You do not have permission');
283 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
284 $this->get('/')->assertSee('You do not have permission');
286 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
288 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
289 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
292 public function test_chapter_delete_restriction()
294 $chapter = $this->entities->chapter();
295 $chapterPage = $chapter->pages->first();
297 $chapterUrl = $chapter->getUrl();
298 $this->actingAs($this->user)
299 ->get($chapterUrl . '/delete')
300 ->assertSee('Delete Chapter');
302 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
304 $this->get($chapterUrl . '/delete')->assertRedirect('/');
305 $this->get('/')->assertSee('You do not have permission');
306 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
307 $this->get('/')->assertSee('You do not have permission');
309 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
311 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
312 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
315 public function test_page_view_restriction()
317 $page = $this->entities->page();
319 $pageUrl = $page->getUrl();
320 $this->actingAs($this->user)->get($pageUrl)->assertOk();
322 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
324 $this->get($pageUrl)->assertSee('Page not found');
326 $this->setRestrictionsForTestRoles($page, ['view']);
328 $this->get($pageUrl)->assertSee($page->name);
331 public function test_page_update_restriction()
333 $page = $this->entities->page();
335 $pageUrl = $page->getUrl();
336 $resp = $this->actingAs($this->user)
337 ->get($pageUrl . '/edit');
338 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
340 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
342 $this->get($pageUrl . '/edit')->assertRedirect('/');
343 $this->get('/')->assertSee('You do not have permission');
345 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
347 $resp = $this->get($pageUrl . '/edit')
349 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
352 public function test_page_delete_restriction()
354 $page = $this->entities->page();
356 $pageUrl = $page->getUrl();
357 $this->actingAs($this->user)
358 ->get($pageUrl . '/delete')
359 ->assertSee('Delete Page');
361 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
363 $this->get($pageUrl . '/delete')->assertRedirect('/');
364 $this->get('/')->assertSee('You do not have permission');
366 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
368 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
371 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
373 /** @var Entity $modelInstance */
374 $modelInstance = $model::query()->first();
375 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
378 $this->put($modelInstance->getUrl('/permissions'), [
381 $permission => 'true',
386 $this->assertDatabaseHas('entity_permissions', [
387 'entity_id' => $modelInstance->id,
388 'entity_type' => $modelInstance->getMorphClass(),
389 'role_id' => $roleId,
394 public function test_bookshelf_restriction_form()
396 $this->entityRestrictionFormTest(Bookshelf::class, 'Shelf Permissions', 'view', '2');
399 public function test_book_restriction_form()
401 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
404 public function test_chapter_restriction_form()
406 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
409 public function test_page_restriction_form()
411 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
414 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
416 $chapter = $this->entities->chapter();
417 $page = $chapter->pages->first();
418 $page2 = $chapter->pages[2];
420 $this->setRestrictionsForTestRoles($page, []);
422 $resp = $this->actingAs($this->user)->get($page2->getUrl());
423 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
426 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
428 $chapter = $this->entities->chapter();
429 $page = $chapter->pages->first();
431 $this->setRestrictionsForTestRoles($page, []);
433 $resp = $this->actingAs($this->user)->get($chapter->getUrl());
434 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
437 public function test_restricted_pages_not_visible_on_chapter_pages()
439 $chapter = $this->entities->chapter();
440 $page = $chapter->pages->first();
442 $this->setRestrictionsForTestRoles($page, []);
444 $this->actingAs($this->user)
445 ->get($chapter->getUrl())
446 ->assertDontSee($page->name);
449 public function test_restricted_chapter_pages_not_visible_on_book_page()
451 $chapter = $this->entities->chapter();
452 $this->actingAs($this->user)
453 ->get($chapter->book->getUrl())
454 ->assertSee($chapter->pages->first()->name);
456 foreach ($chapter->pages as $page) {
457 $this->setRestrictionsForTestRoles($page, []);
460 $this->actingAs($this->user)
461 ->get($chapter->book->getUrl())
462 ->assertDontSee($chapter->pages->first()->name);
465 public function test_bookshelf_update_restriction_override()
467 $shelf = $this->entities->shelf();
469 $this->actingAs($this->viewer)
470 ->get($shelf->getUrl('/edit'))
471 ->assertDontSee('Edit Book');
473 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
475 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
476 $this->get('/')->assertSee('You do not have permission');
478 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
480 $this->get($shelf->getUrl('/edit'))->assertOk();
483 public function test_bookshelf_delete_restriction_override()
485 $shelf = $this->entities->shelf();
487 $this->actingAs($this->viewer)
488 ->get($shelf->getUrl('/delete'))
489 ->assertDontSee('Delete Book');
491 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
493 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
494 $this->get('/')->assertSee('You do not have permission');
496 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
498 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Shelf');
501 public function test_book_create_restriction_override()
503 $book = $this->entities->book();
505 $bookUrl = $book->getUrl();
506 $resp = $this->actingAs($this->viewer)->get($bookUrl);
507 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
508 ->assertElementNotContains('.actions', 'New Chapter');
510 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
512 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
513 $this->get('/')->assertSee('You do not have permission');
514 $this->get($bookUrl . '/create-page')->assertRedirect('/');
515 $this->get('/')->assertSee('You do not have permission');
516 $resp = $this->get($bookUrl);
517 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
518 ->assertElementNotContains('.actions', 'New Chapter');
520 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
522 $resp = $this->post($book->getUrl('/create-chapter'), [
523 'name' => 'test chapter',
524 'description' => 'test desc',
526 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
528 $this->get($book->getUrl('/create-page'));
529 /** @var Page $page */
530 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
531 $resp = $this->post($page->getUrl(), [
532 'name' => 'test page',
533 'html' => 'test desc',
535 $resp->assertRedirect($book->getUrl('/page/test-page'));
537 $resp = $this->get($bookUrl);
538 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
539 ->assertElementContains('.actions', 'New Chapter');
542 public function test_book_update_restriction_override()
544 $book = $this->entities->book();
545 $bookPage = $book->pages->first();
546 $bookChapter = $book->chapters->first();
548 $bookUrl = $book->getUrl();
549 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
550 ->assertDontSee('Edit Book');
552 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
554 $this->get($bookUrl . '/edit')->assertRedirect('/');
555 $this->get('/')->assertSee('You do not have permission');
556 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
557 $this->get('/')->assertSee('You do not have permission');
558 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
559 $this->get('/')->assertSee('You do not have permission');
561 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
563 $this->get($bookUrl . '/edit')->assertOk();
564 $this->get($bookPage->getUrl() . '/edit')->assertOk();
565 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
568 public function test_book_delete_restriction_override()
570 $book = $this->entities->book();
571 $bookPage = $book->pages->first();
572 $bookChapter = $book->chapters->first();
574 $bookUrl = $book->getUrl();
575 $this->actingAs($this->viewer)
576 ->get($bookUrl . '/delete')
577 ->assertDontSee('Delete Book');
579 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
581 $this->get($bookUrl . '/delete')->assertRedirect('/');
582 $this->get('/')->assertSee('You do not have permission');
583 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
584 $this->get('/')->assertSee('You do not have permission');
585 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
586 $this->get('/')->assertSee('You do not have permission');
588 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
590 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
591 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
592 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
595 public function test_page_visible_if_has_permissions_when_book_not_visible()
597 $book = $this->entities->book();
598 $bookChapter = $book->chapters->first();
599 $bookPage = $bookChapter->pages->first();
601 foreach ([$book, $bookChapter, $bookPage] as $entity) {
602 $entity->name = Str::random(24);
606 $this->setRestrictionsForTestRoles($book, []);
607 $this->setRestrictionsForTestRoles($bookPage, ['view']);
609 $this->actingAs($this->viewer);
610 $resp = $this->get($bookPage->getUrl());
612 $resp->assertSee($bookPage->name);
613 $resp->assertDontSee(substr($book->name, 0, 15));
614 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
617 public function test_book_sort_view_permission()
619 /** @var Book $firstBook */
620 $firstBook = Book::query()->first();
621 /** @var Book $secondBook */
622 $secondBook = Book::query()->find(2);
624 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
625 $this->setRestrictionsForTestRoles($secondBook, ['view']);
627 // Test sort page visibility
628 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
629 $this->get('/')->assertSee('You do not have permission');
631 // Check sort page on first book
632 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
635 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
637 $book = $this->entities->book();
638 $this->setRestrictionsForTestRoles($book, []);
639 $bookChapter = $book->chapters->first();
640 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
642 $this->actingAs($this->user)->get($bookChapter->getUrl())
643 ->assertDontSee('New Page');
645 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
647 $this->get($bookChapter->getUrl('/create-page'));
648 /** @var Page $page */
649 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
650 $resp = $this->post($page->getUrl(), [
651 'name' => 'test page',
652 'html' => 'test content',
654 $resp->assertRedirect($book->getUrl('/page/test-page'));