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->entities->setPermissions($entity, $actions, $roles);
42 public function test_bookshelf_view_restriction()
44 $shelf = $this->entities->shelf();
46 $this->actingAs($this->user)
47 ->get($shelf->getUrl())
50 $this->setRestrictionsForTestRoles($shelf, []);
52 $this->followingRedirects()->get($shelf->getUrl())
53 ->assertSee('Shelf not found');
55 $this->setRestrictionsForTestRoles($shelf, ['view']);
57 $this->get($shelf->getUrl())
58 ->assertSee($shelf->name);
61 public function test_bookshelf_update_restriction()
63 $shelf = $this->entities->shelf();
65 $this->actingAs($this->user)
66 ->get($shelf->getUrl('/edit'))
67 ->assertSee('Edit Shelf');
69 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
71 $resp = $this->get($shelf->getUrl('/edit'))
72 ->assertRedirect('/');
73 $this->followRedirects($resp)->assertSee('You do not have permission');
75 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
77 $this->get($shelf->getUrl('/edit'))
81 public function test_bookshelf_delete_restriction()
83 $shelf = $this->entities->shelf();
85 $this->actingAs($this->user)
86 ->get($shelf->getUrl('/delete'))
87 ->assertSee('Delete Shelf');
89 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
91 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
92 $this->get('/')->assertSee('You do not have permission');
94 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
96 $this->get($shelf->getUrl('/delete'))
98 ->assertSee('Delete Shelf');
101 public function test_book_view_restriction()
103 $book = $this->entities->book();
104 $bookPage = $book->pages->first();
105 $bookChapter = $book->chapters->first();
107 $bookUrl = $book->getUrl();
108 $this->actingAs($this->user)
112 $this->setRestrictionsForTestRoles($book, []);
114 $this->followingRedirects()->get($bookUrl)
115 ->assertSee('Book not found');
116 $this->followingRedirects()->get($bookPage->getUrl())
117 ->assertSee('Page not found');
118 $this->followingRedirects()->get($bookChapter->getUrl())
119 ->assertSee('Chapter not found');
121 $this->setRestrictionsForTestRoles($book, ['view']);
124 ->assertSee($book->name);
125 $this->get($bookPage->getUrl())
126 ->assertSee($bookPage->name);
127 $this->get($bookChapter->getUrl())
128 ->assertSee($bookChapter->name);
131 public function test_book_create_restriction()
133 $book = $this->entities->book();
135 $bookUrl = $book->getUrl();
136 $resp = $this->actingAs($this->viewer)->get($bookUrl);
137 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
138 ->assertElementNotContains('.actions', 'New Chapter');
139 $resp = $this->actingAs($this->user)->get($bookUrl);
140 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
141 ->assertElementContains('.actions', 'New Chapter');
143 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
145 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
146 $this->get('/')->assertSee('You do not have permission');
148 $this->get($bookUrl . '/create-page')->assertRedirect('/');
149 $this->get('/')->assertSee('You do not have permission');
151 $resp = $this->get($bookUrl);
152 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
153 ->assertElementNotContains('.actions', 'New Chapter');
155 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
157 $resp = $this->post($book->getUrl('/create-chapter'), [
158 'name' => 'test chapter',
159 'description' => 'desc',
161 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
163 $this->get($book->getUrl('/create-page'));
164 /** @var Page $page */
165 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
166 $resp = $this->post($page->getUrl(), [
167 'name' => 'test page',
168 'html' => 'test content',
170 $resp->assertRedirect($book->getUrl('/page/test-page'));
172 $resp = $this->get($bookUrl);
173 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
174 ->assertElementContains('.actions', 'New Chapter');
177 public function test_book_update_restriction()
179 $book = $this->entities->book();
180 $bookPage = $book->pages->first();
181 $bookChapter = $book->chapters->first();
183 $bookUrl = $book->getUrl();
184 $this->actingAs($this->user)
185 ->get($bookUrl . '/edit')
186 ->assertSee('Edit Book');
188 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
190 $this->get($bookUrl . '/edit')->assertRedirect('/');
191 $this->get('/')->assertSee('You do not have permission');
192 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
193 $this->get('/')->assertSee('You do not have permission');
194 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
195 $this->get('/')->assertSee('You do not have permission');
197 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
199 $this->get($bookUrl . '/edit')->assertOk();
200 $this->get($bookPage->getUrl() . '/edit')->assertOk();
201 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
204 public function test_book_delete_restriction()
206 $book = $this->entities->book();
207 $bookPage = $book->pages->first();
208 $bookChapter = $book->chapters->first();
210 $bookUrl = $book->getUrl();
211 $this->actingAs($this->user)->get($bookUrl . '/delete')
212 ->assertSee('Delete Book');
214 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
216 $this->get($bookUrl . '/delete')->assertRedirect('/');
217 $this->get('/')->assertSee('You do not have permission');
218 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
219 $this->get('/')->assertSee('You do not have permission');
220 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
221 $this->get('/')->assertSee('You do not have permission');
223 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
225 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
226 $this->get($bookPage->getUrl('/delete'))->assertOk()->assertSee('Delete Page');
227 $this->get($bookChapter->getUrl('/delete'))->assertSee('Delete Chapter');
230 public function test_chapter_view_restriction()
232 $chapter = $this->entities->chapter();
233 $chapterPage = $chapter->pages->first();
235 $chapterUrl = $chapter->getUrl();
236 $this->actingAs($this->user)->get($chapterUrl)->assertOk();
238 $this->setRestrictionsForTestRoles($chapter, []);
240 $this->followingRedirects()->get($chapterUrl)->assertSee('Chapter not found');
241 $this->followingRedirects()->get($chapterPage->getUrl())->assertSee('Page not found');
243 $this->setRestrictionsForTestRoles($chapter, ['view']);
245 $this->get($chapterUrl)->assertSee($chapter->name);
246 $this->get($chapterPage->getUrl())->assertSee($chapterPage->name);
249 public function test_chapter_create_restriction()
251 $chapter = $this->entities->chapter();
253 $chapterUrl = $chapter->getUrl();
254 $resp = $this->actingAs($this->user)->get($chapterUrl);
255 $this->withHtml($resp)->assertElementContains('.actions', 'New Page');
257 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete', 'update']);
259 $this->get($chapterUrl . '/create-page')->assertRedirect('/');
260 $this->get('/')->assertSee('You do not have permission');
261 $this->withHtml($this->get($chapterUrl))->assertElementNotContains('.actions', 'New Page');
263 $this->setRestrictionsForTestRoles($chapter, ['view', 'create']);
265 $this->get($chapter->getUrl('/create-page'));
266 /** @var Page $page */
267 $page = Page::query()->where('draft', '=', true)->orderBy('id', 'desc')->first();
268 $resp = $this->post($page->getUrl(), [
269 'name' => 'test page',
270 'html' => 'test content',
272 $resp->assertRedirect($chapter->book->getUrl('/page/test-page'));
274 $this->withHtml($this->get($chapterUrl))->assertElementContains('.actions', 'New Page');
277 public function test_chapter_update_restriction()
279 $chapter = $this->entities->chapter();
280 $chapterPage = $chapter->pages->first();
282 $chapterUrl = $chapter->getUrl();
283 $this->actingAs($this->user)->get($chapterUrl . '/edit')
284 ->assertSee('Edit Chapter');
286 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
288 $this->get($chapterUrl . '/edit')->assertRedirect('/');
289 $this->get('/')->assertSee('You do not have permission');
290 $this->get($chapterPage->getUrl() . '/edit')->assertRedirect('/');
291 $this->get('/')->assertSee('You do not have permission');
293 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
295 $this->get($chapterUrl . '/edit')->assertOk()->assertSee('Edit Chapter');
296 $this->get($chapterPage->getUrl() . '/edit')->assertOk();
299 public function test_chapter_delete_restriction()
301 $chapter = $this->entities->chapter();
302 $chapterPage = $chapter->pages->first();
304 $chapterUrl = $chapter->getUrl();
305 $this->actingAs($this->user)
306 ->get($chapterUrl . '/delete')
307 ->assertSee('Delete Chapter');
309 $this->setRestrictionsForTestRoles($chapter, ['view', 'update']);
311 $this->get($chapterUrl . '/delete')->assertRedirect('/');
312 $this->get('/')->assertSee('You do not have permission');
313 $this->get($chapterPage->getUrl() . '/delete')->assertRedirect('/');
314 $this->get('/')->assertSee('You do not have permission');
316 $this->setRestrictionsForTestRoles($chapter, ['view', 'delete']);
318 $this->get($chapterUrl . '/delete')->assertOk()->assertSee('Delete Chapter');
319 $this->get($chapterPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
322 public function test_page_view_restriction()
324 $page = $this->entities->page();
326 $pageUrl = $page->getUrl();
327 $this->actingAs($this->user)->get($pageUrl)->assertOk();
329 $this->setRestrictionsForTestRoles($page, ['update', 'delete']);
331 $this->get($pageUrl)->assertSee('Page not found');
333 $this->setRestrictionsForTestRoles($page, ['view']);
335 $this->get($pageUrl)->assertSee($page->name);
338 public function test_page_update_restriction()
340 $page = $this->entities->page();
342 $pageUrl = $page->getUrl();
343 $resp = $this->actingAs($this->user)
344 ->get($pageUrl . '/edit');
345 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
347 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
349 $this->get($pageUrl . '/edit')->assertRedirect('/');
350 $this->get('/')->assertSee('You do not have permission');
352 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
354 $resp = $this->get($pageUrl . '/edit')
356 $this->withHtml($resp)->assertElementExists('input[name="name"][value="' . $page->name . '"]');
359 public function test_page_delete_restriction()
361 $page = $this->entities->page();
363 $pageUrl = $page->getUrl();
364 $this->actingAs($this->user)
365 ->get($pageUrl . '/delete')
366 ->assertSee('Delete Page');
368 $this->setRestrictionsForTestRoles($page, ['view', 'update']);
370 $this->get($pageUrl . '/delete')->assertRedirect('/');
371 $this->get('/')->assertSee('You do not have permission');
373 $this->setRestrictionsForTestRoles($page, ['view', 'delete']);
375 $this->get($pageUrl . '/delete')->assertOk()->assertSee('Delete Page');
378 protected function entityRestrictionFormTest(string $model, string $title, string $permission, string $roleId)
380 /** @var Entity $modelInstance */
381 $modelInstance = $model::query()->first();
382 $this->asAdmin()->get($modelInstance->getUrl('/permissions'))
385 $this->put($modelInstance->getUrl('/permissions'), [
386 'restricted' => 'true',
389 $permission => 'true',
394 $this->assertDatabaseHas($modelInstance->getTable(), ['id' => $modelInstance->id, 'restricted' => true]);
395 $this->assertDatabaseHas('entity_permissions', [
396 'restrictable_id' => $modelInstance->id,
397 'restrictable_type' => $modelInstance->getMorphClass(),
398 'role_id' => $roleId,
399 'action' => $permission,
403 public function test_bookshelf_restriction_form()
405 $this->entityRestrictionFormTest(Bookshelf::class, 'Shelf Permissions', 'view', '2');
408 public function test_book_restriction_form()
410 $this->entityRestrictionFormTest(Book::class, 'Book Permissions', 'view', '2');
413 public function test_chapter_restriction_form()
415 $this->entityRestrictionFormTest(Chapter::class, 'Chapter Permissions', 'update', '2');
418 public function test_page_restriction_form()
420 $this->entityRestrictionFormTest(Page::class, 'Page Permissions', 'delete', '2');
423 public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
425 $chapter = $this->entities->chapter();
426 $page = $chapter->pages->first();
427 $page2 = $chapter->pages[2];
429 $this->setRestrictionsForTestRoles($page, []);
431 $resp = $this->actingAs($this->user)->get($page2->getUrl());
432 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
435 public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
437 $chapter = $this->entities->chapter();
438 $page = $chapter->pages->first();
440 $this->setRestrictionsForTestRoles($page, []);
442 $resp = $this->actingAs($this->user)->get($chapter->getUrl());
443 $this->withHtml($resp)->assertElementNotContains('.sidebar-page-list', $page->name);
446 public function test_restricted_pages_not_visible_on_chapter_pages()
448 $chapter = $this->entities->chapter();
449 $page = $chapter->pages->first();
451 $this->setRestrictionsForTestRoles($page, []);
453 $this->actingAs($this->user)
454 ->get($chapter->getUrl())
455 ->assertDontSee($page->name);
458 public function test_restricted_chapter_pages_not_visible_on_book_page()
460 $chapter = $this->entities->chapter();
461 $this->actingAs($this->user)
462 ->get($chapter->book->getUrl())
463 ->assertSee($chapter->pages->first()->name);
465 foreach ($chapter->pages as $page) {
466 $this->setRestrictionsForTestRoles($page, []);
469 $this->actingAs($this->user)
470 ->get($chapter->book->getUrl())
471 ->assertDontSee($chapter->pages->first()->name);
474 public function test_bookshelf_update_restriction_override()
476 $shelf = $this->entities->shelf();
478 $this->actingAs($this->viewer)
479 ->get($shelf->getUrl('/edit'))
480 ->assertDontSee('Edit Book');
482 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
484 $this->get($shelf->getUrl('/edit'))->assertRedirect('/');
485 $this->get('/')->assertSee('You do not have permission');
487 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
489 $this->get($shelf->getUrl('/edit'))->assertOk();
492 public function test_bookshelf_delete_restriction_override()
494 $shelf = $this->entities->shelf();
496 $this->actingAs($this->viewer)
497 ->get($shelf->getUrl('/delete'))
498 ->assertDontSee('Delete Book');
500 $this->setRestrictionsForTestRoles($shelf, ['view', 'update']);
502 $this->get($shelf->getUrl('/delete'))->assertRedirect('/');
503 $this->get('/')->assertSee('You do not have permission');
505 $this->setRestrictionsForTestRoles($shelf, ['view', 'delete']);
507 $this->get($shelf->getUrl('/delete'))->assertOk()->assertSee('Delete Shelf');
510 public function test_book_create_restriction_override()
512 $book = $this->entities->book();
514 $bookUrl = $book->getUrl();
515 $resp = $this->actingAs($this->viewer)->get($bookUrl);
516 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
517 ->assertElementNotContains('.actions', 'New Chapter');
519 $this->setRestrictionsForTestRoles($book, ['view', 'delete', 'update']);
521 $this->get($bookUrl . '/create-chapter')->assertRedirect('/');
522 $this->get('/')->assertSee('You do not have permission');
523 $this->get($bookUrl . '/create-page')->assertRedirect('/');
524 $this->get('/')->assertSee('You do not have permission');
525 $resp = $this->get($bookUrl);
526 $this->withHtml($resp)->assertElementNotContains('.actions', 'New Page')
527 ->assertElementNotContains('.actions', 'New Chapter');
529 $this->setRestrictionsForTestRoles($book, ['view', 'create']);
531 $resp = $this->post($book->getUrl('/create-chapter'), [
532 'name' => 'test chapter',
533 'description' => 'test desc',
535 $resp->assertRedirect($book->getUrl('/chapter/test-chapter'));
537 $this->get($book->getUrl('/create-page'));
538 /** @var Page $page */
539 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
540 $resp = $this->post($page->getUrl(), [
541 'name' => 'test page',
542 'html' => 'test desc',
544 $resp->assertRedirect($book->getUrl('/page/test-page'));
546 $resp = $this->get($bookUrl);
547 $this->withHtml($resp)->assertElementContains('.actions', 'New Page')
548 ->assertElementContains('.actions', 'New Chapter');
551 public function test_book_update_restriction_override()
553 $book = $this->entities->book();
554 $bookPage = $book->pages->first();
555 $bookChapter = $book->chapters->first();
557 $bookUrl = $book->getUrl();
558 $this->actingAs($this->viewer)->get($bookUrl . '/edit')
559 ->assertDontSee('Edit Book');
561 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
563 $this->get($bookUrl . '/edit')->assertRedirect('/');
564 $this->get('/')->assertSee('You do not have permission');
565 $this->get($bookPage->getUrl() . '/edit')->assertRedirect('/');
566 $this->get('/')->assertSee('You do not have permission');
567 $this->get($bookChapter->getUrl() . '/edit')->assertRedirect('/');
568 $this->get('/')->assertSee('You do not have permission');
570 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
572 $this->get($bookUrl . '/edit')->assertOk();
573 $this->get($bookPage->getUrl() . '/edit')->assertOk();
574 $this->get($bookChapter->getUrl() . '/edit')->assertSee('Edit Chapter');
577 public function test_book_delete_restriction_override()
579 $book = $this->entities->book();
580 $bookPage = $book->pages->first();
581 $bookChapter = $book->chapters->first();
583 $bookUrl = $book->getUrl();
584 $this->actingAs($this->viewer)
585 ->get($bookUrl . '/delete')
586 ->assertDontSee('Delete Book');
588 $this->setRestrictionsForTestRoles($book, ['view', 'update']);
590 $this->get($bookUrl . '/delete')->assertRedirect('/');
591 $this->get('/')->assertSee('You do not have permission');
592 $this->get($bookPage->getUrl() . '/delete')->assertRedirect('/');
593 $this->get('/')->assertSee('You do not have permission');
594 $this->get($bookChapter->getUrl() . '/delete')->assertRedirect('/');
595 $this->get('/')->assertSee('You do not have permission');
597 $this->setRestrictionsForTestRoles($book, ['view', 'delete']);
599 $this->get($bookUrl . '/delete')->assertOk()->assertSee('Delete Book');
600 $this->get($bookPage->getUrl() . '/delete')->assertOk()->assertSee('Delete Page');
601 $this->get($bookChapter->getUrl() . '/delete')->assertSee('Delete Chapter');
604 public function test_page_visible_if_has_permissions_when_book_not_visible()
606 $book = $this->entities->book();
607 $bookChapter = $book->chapters->first();
608 $bookPage = $bookChapter->pages->first();
610 foreach ([$book, $bookChapter, $bookPage] as $entity) {
611 $entity->name = Str::random(24);
615 $this->setRestrictionsForTestRoles($book, []);
616 $this->setRestrictionsForTestRoles($bookPage, ['view']);
618 $this->actingAs($this->viewer);
619 $resp = $this->get($bookPage->getUrl());
621 $resp->assertSee($bookPage->name);
622 $resp->assertDontSee(substr($book->name, 0, 15));
623 $resp->assertDontSee(substr($bookChapter->name, 0, 15));
626 public function test_book_sort_view_permission()
628 /** @var Book $firstBook */
629 $firstBook = Book::query()->first();
630 /** @var Book $secondBook */
631 $secondBook = Book::query()->find(2);
633 $this->setRestrictionsForTestRoles($firstBook, ['view', 'update']);
634 $this->setRestrictionsForTestRoles($secondBook, ['view']);
636 // Test sort page visibility
637 $this->actingAs($this->user)->get($secondBook->getUrl('/sort'))->assertRedirect('/');
638 $this->get('/')->assertSee('You do not have permission');
640 // Check sort page on first book
641 $this->actingAs($this->user)->get($firstBook->getUrl('/sort'));
644 public function test_can_create_page_if_chapter_has_permissions_when_book_not_visible()
646 $book = $this->entities->book();
647 $this->setRestrictionsForTestRoles($book, []);
648 $bookChapter = $book->chapters->first();
649 $this->setRestrictionsForTestRoles($bookChapter, ['view']);
651 $this->actingAs($this->user)->get($bookChapter->getUrl())
652 ->assertDontSee('New Page');
654 $this->setRestrictionsForTestRoles($bookChapter, ['view', 'create']);
656 $this->get($bookChapter->getUrl('/create-page'));
657 /** @var Page $page */
658 $page = Page::query()->where('draft', '=', true)->orderByDesc('id')->first();
659 $resp = $this->post($page->getUrl(), [
660 'name' => 'test page',
661 'html' => 'test content',
663 $resp->assertRedirect($book->getUrl('/page/test-page'));