From 8e6248f57f92d943a011c3219120d60ee4f2f00b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 5 Mar 2016 18:09:21 +0000 Subject: [PATCH] Added restriction tests and fixed any bugs in the process Also updated many styles within areas affected by the new permission and roles system. --- app/Exceptions/Handler.php | 3 +- app/Exceptions/NotFoundException.php | 14 + app/Http/Controllers/ChapterController.php | 9 +- app/Http/Controllers/PageController.php | 3 +- app/Repos/BookRepo.php | 13 +- app/Repos/ChapterRepo.php | 14 +- app/Repos/PageRepo.php | 5 +- app/Services/RestrictionService.php | 30 +- app/helpers.php | 9 +- database/seeds/DummyContentSeeder.php | 2 +- phpunit.xml | 1 + resources/assets/sass/_header.scss | 3 + resources/assets/sass/_lists.scss | 3 +- resources/views/books/index.blade.php | 4 +- resources/views/books/restrictions.blade.php | 13 + resources/views/books/show.blade.php | 32 +- .../views/chapters/restrictions.blade.php | 14 + resources/views/chapters/show.blade.php | 27 +- resources/views/errors/404.blade.php | 2 +- .../views/form/restriction-form.blade.php | 3 +- resources/views/pages/restrictions.blade.php | 21 + resources/views/pages/show.blade.php | 31 ++ resources/views/settings/roles/form.blade.php | 12 +- .../views/settings/roles/index.blade.php | 2 +- tests/RestrictionsTest.php | 407 ++++++++++++++++++ tests/TestCase.php | 35 ++ 26 files changed, 680 insertions(+), 32 deletions(-) create mode 100644 app/Exceptions/NotFoundException.php create mode 100644 tests/RestrictionsTest.php diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 73a316953..14d553ed0 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -56,7 +56,8 @@ class Handler extends ExceptionHandler // Which will include the basic message to point the user roughly to the cause. if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); - return response()->view('errors/500', ['message' => $message], 500); + $code = ($e->getCode() === 0) ? 500 : $e->getCode(); + return response()->view('errors/' . $code, ['message' => $message], $code); } return parent::render($request, $e); diff --git a/app/Exceptions/NotFoundException.php b/app/Exceptions/NotFoundException.php new file mode 100644 index 000000000..3c027dc44 --- /dev/null +++ b/app/Exceptions/NotFoundException.php @@ -0,0 +1,14 @@ +bookRepo->getChildren($book); Views::add($chapter); $this->setPageTitle($chapter->getShortName()); - return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]); + $pages = $this->chapterRepo->getChildren($chapter); + return view('chapters/show', [ + 'book' => $book, + 'chapter' => $chapter, + 'current' => $chapter, + 'sidebarTree' => $sidebarTree, + 'pages' => $pages + ]); } /** diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index b469f51dd..19e4744ea 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -1,6 +1,7 @@ pageRepo->getBySlug($pageSlug, $book->id); - } catch (NotFoundHttpException $e) { + } catch (NotFoundException $e) { $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); if ($page === null) abort(404); return redirect($page->getUrl()); diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index 73572f25e..4ae7cc062 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -1,6 +1,7 @@ bookQuery()->where('slug', '=', $slug)->first(); - if ($book === null) abort(404); + if ($book === null) throw new NotFoundException('Book not found'); return $book; } @@ -153,6 +155,7 @@ class BookRepo $this->chapterRepo->destroy($chapter); } $book->views()->delete(); + $book->restrictions()->delete(); $book->delete(); } @@ -210,11 +213,13 @@ class BookRepo public function getChildren(Book $book) { $pageQuery = $book->pages()->where('chapter_id', '=', 0); - $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); + $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); $pages = $pageQuery->get(); - $chapterQuery = $book->chapters()->with('pages'); - $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); + $chapterQuery = $book->chapters()->with(['pages' => function($query) { + $this->restrictionService->enforcePageRestrictions($query, 'view'); + }]); + $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); $chapters = $chapterQuery->get(); $children = $pages->merge($chapters); $bookSlug = $book->slug; diff --git a/app/Repos/ChapterRepo.php b/app/Repos/ChapterRepo.php index 90f2f8c54..095596a60 100644 --- a/app/Repos/ChapterRepo.php +++ b/app/Repos/ChapterRepo.php @@ -2,6 +2,7 @@ use Activity; +use BookStack\Exceptions\NotFoundException; use BookStack\Services\RestrictionService; use Illuminate\Support\Str; use BookStack\Chapter; @@ -66,14 +67,24 @@ class ChapterRepo * @param $slug * @param $bookId * @return mixed + * @throws NotFoundException */ public function getBySlug($slug, $bookId) { $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); - if ($chapter === null) abort(404); + if ($chapter === null) throw new NotFoundException('Chapter not found'); return $chapter; } + /** + * Get the child items for a chapter + * @param Chapter $chapter + */ + public function getChildren(Chapter $chapter) + { + return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get(); + } + /** * Create a new chapter from request input. * @param $input @@ -98,6 +109,7 @@ class ChapterRepo } Activity::removeEntity($chapter); $chapter->views()->delete(); + $chapter->restrictions()->delete(); $chapter->delete(); } diff --git a/app/Repos/PageRepo.php b/app/Repos/PageRepo.php index c4cf00e7c..f3933af69 100644 --- a/app/Repos/PageRepo.php +++ b/app/Repos/PageRepo.php @@ -4,6 +4,7 @@ use Activity; use BookStack\Book; use BookStack\Chapter; +use BookStack\Exceptions\NotFoundException; use BookStack\Services\RestrictionService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -56,11 +57,12 @@ class PageRepo * @param $slug * @param $bookId * @return mixed + * @throws NotFoundException */ public function getBySlug($slug, $bookId) { $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); - if ($page === null) throw new NotFoundHttpException('Page not found'); + if ($page === null) throw new NotFoundException('Page not found'); return $page; } @@ -373,6 +375,7 @@ class PageRepo Activity::removeEntity($page); $page->views()->delete(); $page->revisions()->delete(); + $page->restrictions()->delete(); $page->delete(); } diff --git a/app/Services/RestrictionService.php b/app/Services/RestrictionService.php index 0ef80b229..f7838bf88 100644 --- a/app/Services/RestrictionService.php +++ b/app/Services/RestrictionService.php @@ -15,10 +15,16 @@ class RestrictionService public function __construct() { $user = auth()->user(); - $this->userRoles = $user ? auth()->user()->roles->pluck('id') : false; + $this->userRoles = $user ? auth()->user()->roles->pluck('id') : []; $this->isAdmin = $user ? auth()->user()->hasRole('admin') : false; } + /** + * Checks if an entity has a restriction set upon it. + * @param Entity $entity + * @param $action + * @return bool + */ public function checkIfEntityRestricted(Entity $entity, $action) { if ($this->isAdmin) return true; @@ -93,12 +99,28 @@ class RestrictionService }); }); }) + // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions + ->orWhere(function ($query) { + $query->where('restricted', '=', false) + ->whereExists(function ($query) { + $query->select('*')->from('chapters') + ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false); + }) + ->whereExists(function ($query) { + $query->select('*')->from('books') + ->whereRaw('books.id=pages.book_id') + ->whereExists(function ($query) { + $this->checkRestrictionsQuery($query, 'books', 'Book'); + }); + }); + }) // Page unrestricted, Has a chapter with accepted permissions ->orWhere(function ($query) { $query->where('restricted', '=', false) ->whereExists(function ($query) { $query->select('*')->from('chapters') ->whereRaw('chapters.id=pages.chapter_id') + ->where('restricted', '=', true) ->whereExists(function ($query) { $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); }); @@ -183,8 +205,10 @@ class RestrictionService return $query->where(function ($parentWhereQuery) { $parentWhereQuery ->where('restricted', '=', false) - ->orWhereExists(function ($query) { - $this->checkRestrictionsQuery($query, 'books', 'Book'); + ->orWhere(function ($query) { + $query->where('restricted', '=', true)->whereExists(function ($query) { + $this->checkRestrictionsQuery($query, 'books', 'Book'); + }); }); }); } diff --git a/app/helpers.php b/app/helpers.php index 8f080c5e1..ead6b3008 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1,10 +1,10 @@ check()) return false; if ($ownable === null) { return auth()->user() && auth()->user()->can($permission); } @@ -47,9 +48,9 @@ function userCan($permission, \BookStack\Ownable $ownable = null) $permissionBaseName = strtolower($permission) . '-'; $hasPermission = false; if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; - if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; + if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; - if(!$ownable instanceof \BookStack\Entity) return $hasPermission; + if (!$ownable instanceof \BookStack\Entity) return $hasPermission; // Check restrictions on the entitiy $restrictionService = app('BookStack\Services\RestrictionService'); diff --git a/database/seeds/DummyContentSeeder.php b/database/seeds/DummyContentSeeder.php index aa70eaa0a..328971f26 100644 --- a/database/seeds/DummyContentSeeder.php +++ b/database/seeds/DummyContentSeeder.php @@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder public function run() { $user = factory(BookStack\User::class, 1)->create(); - $role = \BookStack\Role::getDefault(); + $role = \BookStack\Role::getRole('editor'); $user->attachRole($role); diff --git a/phpunit.xml b/phpunit.xml index 762fc2da7..66196e8cf 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,6 +21,7 @@ + diff --git a/resources/assets/sass/_header.scss b/resources/assets/sass/_header.scss index 1edfc0037..87aa20046 100644 --- a/resources/assets/sass/_header.scss +++ b/resources/assets/sass/_header.scss @@ -87,6 +87,9 @@ header { padding-top: $-s; } } + .dropdown-container { + font-size: 0.9em; + } } form.search-box { diff --git a/resources/assets/sass/_lists.scss b/resources/assets/sass/_lists.scss index f0bd3b1ea..09707ebc4 100644 --- a/resources/assets/sass/_lists.scss +++ b/resources/assets/sass/_lists.scss @@ -95,13 +95,14 @@ // Sidebar list .book-tree { - padding: $-xl 0 0 0; + padding: $-l 0 0 0; position: relative; right: 0; top: 0; transition: ease-in-out 240ms; transition-property: right, border; border-left: 0px solid #FFF; + background-color: #FFF; &.fixed { position: fixed; top: 0; diff --git a/resources/views/books/index.blade.php b/resources/views/books/index.blade.php index d5d7cb139..7b5c92b5a 100644 --- a/resources/views/books/index.blade.php +++ b/resources/views/books/index.blade.php @@ -30,7 +30,9 @@ {!! $books->render() !!} @else

No books have been created.

- Create one now + @if(userCan('books-create-all')) + Create one now + @endif @endif
diff --git a/resources/views/books/restrictions.blade.php b/resources/views/books/restrictions.blade.php index 826f218ce..60b126a7b 100644 --- a/resources/views/books/restrictions.blade.php +++ b/resources/views/books/restrictions.blade.php @@ -2,6 +2,19 @@ @section('content') +
+
+ +
+
+ +

Book Restrictions

@include('form/restriction-form', ['model' => $book]) diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index f8a22ada8..cd32a406b 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -2,7 +2,7 @@ @section('content') -
+
@@ -15,13 +15,22 @@ @endif @if(userCan('book-update', $book)) Edit - Sort @endif - @if(userCan('restrictions-manage', $book)) - Restrict - @endif - @if(userCan('book-delete', $book)) - Delete + @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book)) + @endif
@@ -78,6 +87,15 @@
+ @if($book->restricted) +

+ @if(userCan('restrictions-manage', $book)) + Book Restricted + @else + Book Restricted + @endif +

+ @endif diff --git a/resources/views/settings/roles/form.blade.php b/resources/views/settings/roles/form.blade.php index ed0e3dd91..fafb9bed2 100644 --- a/resources/views/settings/roles/form.blade.php +++ b/resources/views/settings/roles/form.blade.php @@ -3,6 +3,7 @@
+

Role Details

@include('form/text', ['name' => 'display_name']) @@ -11,7 +12,7 @@ @include('form/text', ['name' => 'description'])
-
+

System Permissions

@@ -33,10 +34,17 @@
+
+ +

Asset Permissions

+

+ These permissions control default access to the assets within the system.
+ Restrictions on Books, Chapters and Pages will override these permissions. +

@@ -104,4 +112,6 @@ + +Cancel \ No newline at end of file diff --git a/resources/views/settings/roles/index.blade.php b/resources/views/settings/roles/index.blade.php index 601c6533e..8f92a5eba 100644 --- a/resources/views/settings/roles/index.blade.php +++ b/resources/views/settings/roles/index.blade.php @@ -4,7 +4,7 @@ @include('settings/navbar', ['selected' => 'roles']) -
+

User Roles

diff --git a/tests/RestrictionsTest.php b/tests/RestrictionsTest.php new file mode 100644 index 000000000..40b5a7647 --- /dev/null +++ b/tests/RestrictionsTest.php @@ -0,0 +1,407 @@ +user = $this->getNewUser(); + } + + /** + * Manually set some restrictions on an entity. + * @param \BookStack\Entity $entity + * @param $actions + */ + protected function setEntityRestrictions(\BookStack\Entity $entity, $actions) + { + $entity->restricted = true; + $entity->restrictions()->delete(); + $role = $this->user->roles->first(); + foreach ($actions as $action) { + $entity->restrictions()->create([ + 'role_id' => $role->id, + 'action' => strtolower($action) + ]); + } + $entity->save(); + $entity->load('restrictions'); + } + + public function test_book_view_restriction() + { + $book = \BookStack\Book::first(); + $bookPage = $book->pages->first(); + $bookChapter = $book->chapters->first(); + + $bookUrl = $book->getUrl(); + $this->actingAs($this->user) + ->visit($bookUrl) + ->seePageIs($bookUrl); + + $this->setEntityRestrictions($book, []); + + $this->forceVisit($bookUrl) + ->see('Book not found'); + $this->forceVisit($bookPage->getUrl()) + ->see('Book not found'); + $this->forceVisit($bookChapter->getUrl()) + ->see('Book not found'); + + $this->setEntityRestrictions($book, ['view']); + + $this->visit($bookUrl) + ->see($book->name); + $this->visit($bookPage->getUrl()) + ->see($bookPage->name); + $this->visit($bookChapter->getUrl()) + ->see($bookChapter->name); + } + + public function test_book_create_restriction() + { + $book = \BookStack\Book::first(); + + $bookUrl = $book->getUrl(); + $this->actingAs($this->user) + ->visit($bookUrl) + ->seeInElement('.action-buttons', 'New Page') + ->seeInElement('.action-buttons', 'New Chapter'); + + $this->setEntityRestrictions($book, ['view', 'delete', 'update']); + + $this->forceVisit($bookUrl . '/chapter/create') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($bookUrl . '/page/create') + ->see('You do not have permission')->seePageIs('/'); + $this->visit($bookUrl)->dontSeeInElement('.action-buttons', 'New Page') + ->dontSeeInElement('.action-buttons', 'New Chapter'); + + $this->setEntityRestrictions($book, ['view', 'create']); + + $this->visit($bookUrl . '/chapter/create') + ->type('test chapter', 'name') + ->type('test description for chapter', 'description') + ->press('Save Chapter') + ->seePageIs($bookUrl . '/chapter/test-chapter'); + $this->visit($bookUrl . '/page/create') + ->type('test page', 'name') + ->type('test content', 'html') + ->press('Save Page') + ->seePageIs($bookUrl . '/page/test-page'); + $this->visit($bookUrl)->seeInElement('.action-buttons', 'New Page') + ->seeInElement('.action-buttons', 'New Chapter'); + } + + public function test_book_update_restriction() + { + $book = \BookStack\Book::first(); + $bookPage = $book->pages->first(); + $bookChapter = $book->chapters->first(); + + $bookUrl = $book->getUrl(); + $this->actingAs($this->user) + ->visit($bookUrl . '/edit') + ->see('Edit Book'); + + $this->setEntityRestrictions($book, ['view', 'delete']); + + $this->forceVisit($bookUrl . '/edit') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($bookPage->getUrl() . '/edit') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($bookChapter->getUrl() . '/edit') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($book, ['view', 'update']); + + $this->visit($bookUrl . '/edit') + ->seePageIs($bookUrl . '/edit'); + $this->visit($bookPage->getUrl() . '/edit') + ->seePageIs($bookPage->getUrl() . '/edit'); + $this->visit($bookChapter->getUrl() . '/edit') + ->see('Edit Chapter'); + } + + public function test_book_delete_restriction() + { + $book = \BookStack\Book::first(); + $bookPage = $book->pages->first(); + $bookChapter = $book->chapters->first(); + + $bookUrl = $book->getUrl(); + $this->actingAs($this->user) + ->visit($bookUrl . '/delete') + ->see('Delete Book'); + + $this->setEntityRestrictions($book, ['view', 'update']); + + $this->forceVisit($bookUrl . '/delete') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($bookPage->getUrl() . '/delete') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($bookChapter->getUrl() . '/delete') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($book, ['view', 'delete']); + + $this->visit($bookUrl . '/delete') + ->seePageIs($bookUrl . '/delete')->see('Delete Book'); + $this->visit($bookPage->getUrl() . '/delete') + ->seePageIs($bookPage->getUrl() . '/delete')->see('Delete Page'); + $this->visit($bookChapter->getUrl() . '/delete') + ->see('Delete Chapter'); + } + + public function test_chapter_view_restriction() + { + $chapter = \BookStack\Chapter::first(); + $chapterPage = $chapter->pages->first(); + + $chapterUrl = $chapter->getUrl(); + $this->actingAs($this->user) + ->visit($chapterUrl) + ->seePageIs($chapterUrl); + + $this->setEntityRestrictions($chapter, []); + + $this->forceVisit($chapterUrl) + ->see('Chapter not found'); + $this->forceVisit($chapterPage->getUrl()) + ->see('Page not found'); + + $this->setEntityRestrictions($chapter, ['view']); + + $this->visit($chapterUrl) + ->see($chapter->name); + $this->visit($chapterPage->getUrl()) + ->see($chapterPage->name); + } + + public function test_chapter_create_restriction() + { + $chapter = \BookStack\Chapter::first(); + + $chapterUrl = $chapter->getUrl(); + $this->actingAs($this->user) + ->visit($chapterUrl) + ->seeInElement('.action-buttons', 'New Page'); + + $this->setEntityRestrictions($chapter, ['view', 'delete', 'update']); + + $this->forceVisit($chapterUrl . '/create-page') + ->see('You do not have permission')->seePageIs('/'); + $this->visit($chapterUrl)->dontSeeInElement('.action-buttons', 'New Page'); + + $this->setEntityRestrictions($chapter, ['view', 'create']); + + + $this->visit($chapterUrl . '/create-page') + ->type('test page', 'name') + ->type('test content', 'html') + ->press('Save Page') + ->seePageIs($chapter->book->getUrl() . '/page/test-page'); + $this->visit($chapterUrl)->seeInElement('.action-buttons', 'New Page'); + } + + public function test_chapter_update_restriction() + { + $chapter = \BookStack\Chapter::first(); + $chapterPage = $chapter->pages->first(); + + $chapterUrl = $chapter->getUrl(); + $this->actingAs($this->user) + ->visit($chapterUrl . '/edit') + ->see('Edit Chapter'); + + $this->setEntityRestrictions($chapter, ['view', 'delete']); + + $this->forceVisit($chapterUrl . '/edit') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($chapterPage->getUrl() . '/edit') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($chapter, ['view', 'update']); + + $this->visit($chapterUrl . '/edit') + ->seePageIs($chapterUrl . '/edit')->see('Edit Chapter'); + $this->visit($chapterPage->getUrl() . '/edit') + ->seePageIs($chapterPage->getUrl() . '/edit'); + } + + public function test_chapter_delete_restriction() + { + $chapter = \BookStack\Chapter::first(); + $chapterPage = $chapter->pages->first(); + + $chapterUrl = $chapter->getUrl(); + $this->actingAs($this->user) + ->visit($chapterUrl . '/delete') + ->see('Delete Chapter'); + + $this->setEntityRestrictions($chapter, ['view', 'update']); + + $this->forceVisit($chapterUrl . '/delete') + ->see('You do not have permission')->seePageIs('/'); + $this->forceVisit($chapterPage->getUrl() . '/delete') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($chapter, ['view', 'delete']); + + $this->visit($chapterUrl . '/delete') + ->seePageIs($chapterUrl . '/delete')->see('Delete Chapter'); + $this->visit($chapterPage->getUrl() . '/delete') + ->seePageIs($chapterPage->getUrl() . '/delete')->see('Delete Page'); + } + + public function test_page_view_restriction() + { + $page = \BookStack\Page::first(); + + $pageUrl = $page->getUrl(); + $this->actingAs($this->user) + ->visit($pageUrl) + ->seePageIs($pageUrl); + + $this->setEntityRestrictions($page, ['update', 'delete']); + + $this->forceVisit($pageUrl) + ->see('Page not found'); + + $this->setEntityRestrictions($page, ['view']); + + $this->visit($pageUrl) + ->see($page->name); + } + + public function test_page_update_restriction() + { + $page = \BookStack\Chapter::first(); + + $pageUrl = $page->getUrl(); + $this->actingAs($this->user) + ->visit($pageUrl . '/edit') + ->seeInField('name', $page->name); + + $this->setEntityRestrictions($page, ['view', 'delete']); + + $this->forceVisit($pageUrl . '/edit') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($page, ['view', 'update']); + + $this->visit($pageUrl . '/edit') + ->seePageIs($pageUrl . '/edit')->seeInField('name', $page->name); + } + + public function test_page_delete_restriction() + { + $page = \BookStack\Page::first(); + + $pageUrl = $page->getUrl(); + $this->actingAs($this->user) + ->visit($pageUrl . '/delete') + ->see('Delete Page'); + + $this->setEntityRestrictions($page, ['view', 'update']); + + $this->forceVisit($pageUrl . '/delete') + ->see('You do not have permission')->seePageIs('/'); + + $this->setEntityRestrictions($page, ['view', 'delete']); + + $this->visit($pageUrl . '/delete') + ->seePageIs($pageUrl . '/delete')->see('Delete Page'); + } + + public function test_book_restriction_form() + { + $book = \BookStack\Book::first(); + $this->asAdmin()->visit($book->getUrl() . '/restrict') + ->see('Book Restrictions') + ->check('restricted') + ->check('restrictions[2][view]') + ->press('Save Restrictions') + ->seeInDatabase('books', ['id' => $book->id, 'restricted' => true]) + ->seeInDatabase('restrictions', [ + 'restrictable_id' => $book->id, + 'restrictable_type' => 'BookStack\Book', + 'role_id' => '2', + 'action' => 'view' + ]); + } + + public function test_chapter_restriction_form() + { + $chapter = \BookStack\Chapter::first(); + $this->asAdmin()->visit($chapter->getUrl() . '/restrict') + ->see('Chapter Restrictions') + ->check('restricted') + ->check('restrictions[2][update]') + ->press('Save Restrictions') + ->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true]) + ->seeInDatabase('restrictions', [ + 'restrictable_id' => $chapter->id, + 'restrictable_type' => 'BookStack\Chapter', + 'role_id' => '2', + 'action' => 'update' + ]); + } + + public function test_page_restriction_form() + { + $page = \BookStack\Page::first(); + $this->asAdmin()->visit($page->getUrl() . '/restrict') + ->see('Page Restrictions') + ->check('restricted') + ->check('restrictions[2][delete]') + ->press('Save Restrictions') + ->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true]) + ->seeInDatabase('restrictions', [ + 'restrictable_id' => $page->id, + 'restrictable_type' => 'BookStack\Page', + 'role_id' => '2', + 'action' => 'delete' + ]); + } + + public function test_restricted_pages_not_visible_in_book_navigation_on_pages() + { + $chapter = \BookStack\Chapter::first(); + $page = $chapter->pages->first(); + $page2 = $chapter->pages[2]; + + $this->setEntityRestrictions($page, []); + + $this->actingAs($this->user) + ->visit($page2->getUrl()) + ->dontSeeInElement('.sidebar-page-list', $page->name); + } + + public function test_restricted_pages_not_visible_in_book_navigation_on_chapters() + { + $chapter = \BookStack\Chapter::first(); + $page = $chapter->pages->first(); + + $this->setEntityRestrictions($page, []); + + $this->actingAs($this->user) + ->visit($chapter->getUrl()) + ->dontSeeInElement('.sidebar-page-list', $page->name); + } + + public function test_restricted_pages_not_visible_on_chapter_pages() + { + $chapter = \BookStack\Chapter::first(); + $page = $chapter->pages->first(); + + $this->setEntityRestrictions($page, []); + + $this->actingAs($this->user) + ->visit($chapter->getUrl()) + ->dontSee($page->name); + } + +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 840fe0d08..567dc93ec 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,6 +1,7 @@ assertEquals( + $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n" + ); + + return $this; + } + + /** + * Do a forced visit that does not error out on exception. + * @param string $uri + * @param array $parameters + * @param array $cookies + * @param array $files + * @return $this + */ + protected function forceVisit($uri, $parameters = [], $cookies = [], $files = []) + { + $method = 'GET'; + $uri = $this->prepareUrlForRequest($uri); + $this->call($method, $uri, $parameters, $cookies, $files); + $this->clearInputs()->followRedirects(); + $this->currentUri = $this->app->make('request')->fullUrl(); + $this->crawler = new Crawler($this->response->getContent(), $uri); + return $this; + } + /** * Click the text within the selected element. * @param $parentElement