Dan Brown

Added restriction tests and fixed any bugs in the process

Also updated many styles within areas affected by the new permission and roles system.
...@@ -56,7 +56,8 @@ class Handler extends ExceptionHandler ...@@ -56,7 +56,8 @@ class Handler extends ExceptionHandler
56 // Which will include the basic message to point the user roughly to the cause. 56 // Which will include the basic message to point the user roughly to the cause.
57 if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) { 57 if (($e instanceof PrettyException || $e->getPrevious() instanceof PrettyException) && !config('app.debug')) {
58 $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage(); 58 $message = ($e instanceof PrettyException) ? $e->getMessage() : $e->getPrevious()->getMessage();
59 - return response()->view('errors/500', ['message' => $message], 500); 59 + $code = ($e->getCode() === 0) ? 500 : $e->getCode();
60 + return response()->view('errors/' . $code, ['message' => $message], $code);
60 } 61 }
61 62
62 return parent::render($request, $e); 63 return parent::render($request, $e);
......
1 +<?php namespace BookStack\Exceptions;
2 +
3 +
4 +class NotFoundException extends PrettyException {
5 +
6 + /**
7 + * NotFoundException constructor.
8 + * @param string $message
9 + */
10 + public function __construct($message = 'Item not found')
11 + {
12 + parent::__construct($message, 404);
13 + }
14 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -80,7 +80,14 @@ class ChapterController extends Controller ...@@ -80,7 +80,14 @@ class ChapterController extends Controller
80 $sidebarTree = $this->bookRepo->getChildren($book); 80 $sidebarTree = $this->bookRepo->getChildren($book);
81 Views::add($chapter); 81 Views::add($chapter);
82 $this->setPageTitle($chapter->getShortName()); 82 $this->setPageTitle($chapter->getShortName());
83 - return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]); 83 + $pages = $this->chapterRepo->getChildren($chapter);
84 + return view('chapters/show', [
85 + 'book' => $book,
86 + 'chapter' => $chapter,
87 + 'current' => $chapter,
88 + 'sidebarTree' => $sidebarTree,
89 + 'pages' => $pages
90 + ]);
84 } 91 }
85 92
86 /** 93 /**
......
1 <?php namespace BookStack\Http\Controllers; 1 <?php namespace BookStack\Http\Controllers;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Exceptions\NotFoundException;
4 use BookStack\Repos\UserRepo; 5 use BookStack\Repos\UserRepo;
5 use BookStack\Services\ExportService; 6 use BookStack\Services\ExportService;
6 use Illuminate\Http\Request; 7 use Illuminate\Http\Request;
...@@ -94,7 +95,7 @@ class PageController extends Controller ...@@ -94,7 +95,7 @@ class PageController extends Controller
94 95
95 try { 96 try {
96 $page = $this->pageRepo->getBySlug($pageSlug, $book->id); 97 $page = $this->pageRepo->getBySlug($pageSlug, $book->id);
97 - } catch (NotFoundHttpException $e) { 98 + } catch (NotFoundException $e) {
98 $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug); 99 $page = $this->pageRepo->findPageUsingOldSlug($pageSlug, $bookSlug);
99 if ($page === null) abort(404); 100 if ($page === null) abort(404);
100 return redirect($page->getUrl()); 101 return redirect($page->getUrl());
......
1 <?php namespace BookStack\Repos; 1 <?php namespace BookStack\Repos;
2 2
3 use Activity; 3 use Activity;
4 +use BookStack\Exceptions\NotFoundException;
4 use BookStack\Services\RestrictionService; 5 use BookStack\Services\RestrictionService;
5 use Illuminate\Support\Str; 6 use Illuminate\Support\Str;
6 use BookStack\Book; 7 use BookStack\Book;
...@@ -111,11 +112,12 @@ class BookRepo ...@@ -111,11 +112,12 @@ class BookRepo
111 * Get a book by slug 112 * Get a book by slug
112 * @param $slug 113 * @param $slug
113 * @return mixed 114 * @return mixed
115 + * @throws NotFoundException
114 */ 116 */
115 public function getBySlug($slug) 117 public function getBySlug($slug)
116 { 118 {
117 $book = $this->bookQuery()->where('slug', '=', $slug)->first(); 119 $book = $this->bookQuery()->where('slug', '=', $slug)->first();
118 - if ($book === null) abort(404); 120 + if ($book === null) throw new NotFoundException('Book not found');
119 return $book; 121 return $book;
120 } 122 }
121 123
...@@ -153,6 +155,7 @@ class BookRepo ...@@ -153,6 +155,7 @@ class BookRepo
153 $this->chapterRepo->destroy($chapter); 155 $this->chapterRepo->destroy($chapter);
154 } 156 }
155 $book->views()->delete(); 157 $book->views()->delete();
158 + $book->restrictions()->delete();
156 $book->delete(); 159 $book->delete();
157 } 160 }
158 161
...@@ -210,11 +213,13 @@ class BookRepo ...@@ -210,11 +213,13 @@ class BookRepo
210 public function getChildren(Book $book) 213 public function getChildren(Book $book)
211 { 214 {
212 $pageQuery = $book->pages()->where('chapter_id', '=', 0); 215 $pageQuery = $book->pages()->where('chapter_id', '=', 0);
213 - $this->restrictionService->enforcePageRestrictions($pageQuery, 'view'); 216 + $pageQuery = $this->restrictionService->enforcePageRestrictions($pageQuery, 'view');
214 $pages = $pageQuery->get(); 217 $pages = $pageQuery->get();
215 218
216 - $chapterQuery = $book->chapters()->with('pages'); 219 + $chapterQuery = $book->chapters()->with(['pages' => function($query) {
217 - $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view'); 220 + $this->restrictionService->enforcePageRestrictions($query, 'view');
221 + }]);
222 + $chapterQuery = $this->restrictionService->enforceChapterRestrictions($chapterQuery, 'view');
218 $chapters = $chapterQuery->get(); 223 $chapters = $chapterQuery->get();
219 $children = $pages->merge($chapters); 224 $children = $pages->merge($chapters);
220 $bookSlug = $book->slug; 225 $bookSlug = $book->slug;
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 3
4 use Activity; 4 use Activity;
5 +use BookStack\Exceptions\NotFoundException;
5 use BookStack\Services\RestrictionService; 6 use BookStack\Services\RestrictionService;
6 use Illuminate\Support\Str; 7 use Illuminate\Support\Str;
7 use BookStack\Chapter; 8 use BookStack\Chapter;
...@@ -66,15 +67,25 @@ class ChapterRepo ...@@ -66,15 +67,25 @@ class ChapterRepo
66 * @param $slug 67 * @param $slug
67 * @param $bookId 68 * @param $bookId
68 * @return mixed 69 * @return mixed
70 + * @throws NotFoundException
69 */ 71 */
70 public function getBySlug($slug, $bookId) 72 public function getBySlug($slug, $bookId)
71 { 73 {
72 $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); 74 $chapter = $this->chapterQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
73 - if ($chapter === null) abort(404); 75 + if ($chapter === null) throw new NotFoundException('Chapter not found');
74 return $chapter; 76 return $chapter;
75 } 77 }
76 78
77 /** 79 /**
80 + * Get the child items for a chapter
81 + * @param Chapter $chapter
82 + */
83 + public function getChildren(Chapter $chapter)
84 + {
85 + return $this->restrictionService->enforcePageRestrictions($chapter->pages())->get();
86 + }
87 +
88 + /**
78 * Create a new chapter from request input. 89 * Create a new chapter from request input.
79 * @param $input 90 * @param $input
80 * @return $this 91 * @return $this
...@@ -98,6 +109,7 @@ class ChapterRepo ...@@ -98,6 +109,7 @@ class ChapterRepo
98 } 109 }
99 Activity::removeEntity($chapter); 110 Activity::removeEntity($chapter);
100 $chapter->views()->delete(); 111 $chapter->views()->delete();
112 + $chapter->restrictions()->delete();
101 $chapter->delete(); 113 $chapter->delete();
102 } 114 }
103 115
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
4 use Activity; 4 use Activity;
5 use BookStack\Book; 5 use BookStack\Book;
6 use BookStack\Chapter; 6 use BookStack\Chapter;
7 +use BookStack\Exceptions\NotFoundException;
7 use BookStack\Services\RestrictionService; 8 use BookStack\Services\RestrictionService;
8 use Illuminate\Http\Request; 9 use Illuminate\Http\Request;
9 use Illuminate\Support\Facades\Auth; 10 use Illuminate\Support\Facades\Auth;
...@@ -56,11 +57,12 @@ class PageRepo ...@@ -56,11 +57,12 @@ class PageRepo
56 * @param $slug 57 * @param $slug
57 * @param $bookId 58 * @param $bookId
58 * @return mixed 59 * @return mixed
60 + * @throws NotFoundException
59 */ 61 */
60 public function getBySlug($slug, $bookId) 62 public function getBySlug($slug, $bookId)
61 { 63 {
62 $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first(); 64 $page = $this->pageQuery()->where('slug', '=', $slug)->where('book_id', '=', $bookId)->first();
63 - if ($page === null) throw new NotFoundHttpException('Page not found'); 65 + if ($page === null) throw new NotFoundException('Page not found');
64 return $page; 66 return $page;
65 } 67 }
66 68
...@@ -373,6 +375,7 @@ class PageRepo ...@@ -373,6 +375,7 @@ class PageRepo
373 Activity::removeEntity($page); 375 Activity::removeEntity($page);
374 $page->views()->delete(); 376 $page->views()->delete();
375 $page->revisions()->delete(); 377 $page->revisions()->delete();
378 + $page->restrictions()->delete();
376 $page->delete(); 379 $page->delete();
377 } 380 }
378 381
......
...@@ -15,10 +15,16 @@ class RestrictionService ...@@ -15,10 +15,16 @@ class RestrictionService
15 public function __construct() 15 public function __construct()
16 { 16 {
17 $user = auth()->user(); 17 $user = auth()->user();
18 - $this->userRoles = $user ? auth()->user()->roles->pluck('id') : false; 18 + $this->userRoles = $user ? auth()->user()->roles->pluck('id') : [];
19 $this->isAdmin = $user ? auth()->user()->hasRole('admin') : false; 19 $this->isAdmin = $user ? auth()->user()->hasRole('admin') : false;
20 } 20 }
21 21
22 + /**
23 + * Checks if an entity has a restriction set upon it.
24 + * @param Entity $entity
25 + * @param $action
26 + * @return bool
27 + */
22 public function checkIfEntityRestricted(Entity $entity, $action) 28 public function checkIfEntityRestricted(Entity $entity, $action)
23 { 29 {
24 if ($this->isAdmin) return true; 30 if ($this->isAdmin) return true;
...@@ -93,12 +99,28 @@ class RestrictionService ...@@ -93,12 +99,28 @@ class RestrictionService
93 }); 99 });
94 }); 100 });
95 }) 101 })
102 + // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions
103 + ->orWhere(function ($query) {
104 + $query->where('restricted', '=', false)
105 + ->whereExists(function ($query) {
106 + $query->select('*')->from('chapters')
107 + ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false);
108 + })
109 + ->whereExists(function ($query) {
110 + $query->select('*')->from('books')
111 + ->whereRaw('books.id=pages.book_id')
112 + ->whereExists(function ($query) {
113 + $this->checkRestrictionsQuery($query, 'books', 'Book');
114 + });
115 + });
116 + })
96 // Page unrestricted, Has a chapter with accepted permissions 117 // Page unrestricted, Has a chapter with accepted permissions
97 ->orWhere(function ($query) { 118 ->orWhere(function ($query) {
98 $query->where('restricted', '=', false) 119 $query->where('restricted', '=', false)
99 ->whereExists(function ($query) { 120 ->whereExists(function ($query) {
100 $query->select('*')->from('chapters') 121 $query->select('*')->from('chapters')
101 ->whereRaw('chapters.id=pages.chapter_id') 122 ->whereRaw('chapters.id=pages.chapter_id')
123 + ->where('restricted', '=', true)
102 ->whereExists(function ($query) { 124 ->whereExists(function ($query) {
103 $this->checkRestrictionsQuery($query, 'chapters', 'Chapter'); 125 $this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
104 }); 126 });
...@@ -183,10 +205,12 @@ class RestrictionService ...@@ -183,10 +205,12 @@ class RestrictionService
183 return $query->where(function ($parentWhereQuery) { 205 return $query->where(function ($parentWhereQuery) {
184 $parentWhereQuery 206 $parentWhereQuery
185 ->where('restricted', '=', false) 207 ->where('restricted', '=', false)
186 - ->orWhereExists(function ($query) { 208 + ->orWhere(function ($query) {
209 + $query->where('restricted', '=', true)->whereExists(function ($query) {
187 $this->checkRestrictionsQuery($query, 'books', 'Book'); 210 $this->checkRestrictionsQuery($query, 'books', 'Book');
188 }); 211 });
189 }); 212 });
213 + });
190 } 214 }
191 215
192 /** 216 /**
......
1 <?php 1 <?php
2 2
3 -if (! function_exists('versioned_asset')) { 3 +if (!function_exists('versioned_asset')) {
4 /** 4 /**
5 * Get the path to a versioned file. 5 * Get the path to a versioned file.
6 * 6 *
...@@ -39,6 +39,7 @@ if (! function_exists('versioned_asset')) { ...@@ -39,6 +39,7 @@ if (! function_exists('versioned_asset')) {
39 */ 39 */
40 function userCan($permission, \BookStack\Ownable $ownable = null) 40 function userCan($permission, \BookStack\Ownable $ownable = null)
41 { 41 {
42 + if (!auth()->check()) return false;
42 if ($ownable === null) { 43 if ($ownable === null) {
43 return auth()->user() && auth()->user()->can($permission); 44 return auth()->user() && auth()->user()->can($permission);
44 } 45 }
...@@ -47,9 +48,9 @@ function userCan($permission, \BookStack\Ownable $ownable = null) ...@@ -47,9 +48,9 @@ function userCan($permission, \BookStack\Ownable $ownable = null)
47 $permissionBaseName = strtolower($permission) . '-'; 48 $permissionBaseName = strtolower($permission) . '-';
48 $hasPermission = false; 49 $hasPermission = false;
49 if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true; 50 if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true;
50 - if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true; 51 + if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true;
51 52
52 - if(!$ownable instanceof \BookStack\Entity) return $hasPermission; 53 + if (!$ownable instanceof \BookStack\Entity) return $hasPermission;
53 54
54 // Check restrictions on the entitiy 55 // Check restrictions on the entitiy
55 $restrictionService = app('BookStack\Services\RestrictionService'); 56 $restrictionService = app('BookStack\Services\RestrictionService');
......
...@@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder ...@@ -12,7 +12,7 @@ class DummyContentSeeder extends Seeder
12 public function run() 12 public function run()
13 { 13 {
14 $user = factory(BookStack\User::class, 1)->create(); 14 $user = factory(BookStack\User::class, 1)->create();
15 - $role = \BookStack\Role::getDefault(); 15 + $role = \BookStack\Role::getRole('editor');
16 $user->attachRole($role); 16 $user->attachRole($role);
17 17
18 18
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
21 </filter> 21 </filter>
22 <php> 22 <php>
23 <env name="APP_ENV" value="testing"/> 23 <env name="APP_ENV" value="testing"/>
24 + <env name="APP_DEBUG" value="false"/>
24 <env name="CACHE_DRIVER" value="array"/> 25 <env name="CACHE_DRIVER" value="array"/>
25 <env name="SESSION_DRIVER" value="array"/> 26 <env name="SESSION_DRIVER" value="array"/>
26 <env name="QUEUE_DRIVER" value="sync"/> 27 <env name="QUEUE_DRIVER" value="sync"/>
......
...@@ -87,6 +87,9 @@ header { ...@@ -87,6 +87,9 @@ header {
87 padding-top: $-s; 87 padding-top: $-s;
88 } 88 }
89 } 89 }
90 + .dropdown-container {
91 + font-size: 0.9em;
92 + }
90 } 93 }
91 94
92 form.search-box { 95 form.search-box {
......
...@@ -95,13 +95,14 @@ ...@@ -95,13 +95,14 @@
95 95
96 // Sidebar list 96 // Sidebar list
97 .book-tree { 97 .book-tree {
98 - padding: $-xl 0 0 0; 98 + padding: $-l 0 0 0;
99 position: relative; 99 position: relative;
100 right: 0; 100 right: 0;
101 top: 0; 101 top: 0;
102 transition: ease-in-out 240ms; 102 transition: ease-in-out 240ms;
103 transition-property: right, border; 103 transition-property: right, border;
104 border-left: 0px solid #FFF; 104 border-left: 0px solid #FFF;
105 + background-color: #FFF;
105 &.fixed { 106 &.fixed {
106 position: fixed; 107 position: fixed;
107 top: 0; 108 top: 0;
......
...@@ -30,8 +30,10 @@ ...@@ -30,8 +30,10 @@
30 {!! $books->render() !!} 30 {!! $books->render() !!}
31 @else 31 @else
32 <p class="text-muted">No books have been created.</p> 32 <p class="text-muted">No books have been created.</p>
33 + @if(userCan('books-create-all'))
33 <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a> 34 <a href="/books/create" class="text-pos"><i class="zmdi zmdi-edit"></i>Create one now</a>
34 @endif 35 @endif
36 + @endif
35 </div> 37 </div>
36 <div class="col-sm-4 col-sm-offset-1"> 38 <div class="col-sm-4 col-sm-offset-1">
37 <div id="recents"> 39 <div id="recents">
......
...@@ -2,6 +2,19 @@ ...@@ -2,6 +2,19 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{$book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $book->getShortName() }}</a>
11 + </div>
12 + </div>
13 + </div>
14 + </div>
15 + </div>
16 +
17 +
5 <div class="container" ng-non-bindable> 18 <div class="container" ng-non-bindable>
6 <h1>Book Restrictions</h1> 19 <h1>Book Restrictions</h1>
7 @include('form/restriction-form', ['model' => $book]) 20 @include('form/restriction-form', ['model' => $book])
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 - <div class="faded-small toolbar" ng-non-bindable> 5 + <div class="faded-small toolbar">
6 <div class="container"> 6 <div class="container">
7 <div class="row"> 7 <div class="row">
8 <div class="col-md-12"> 8 <div class="col-md-12">
...@@ -15,13 +15,22 @@ ...@@ -15,13 +15,22 @@
15 @endif 15 @endif
16 @if(userCan('book-update', $book)) 16 @if(userCan('book-update', $book))
17 <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a> 17 <a href="{{$book->getEditUrl()}}" class="text-primary text-button"><i class="zmdi zmdi-edit"></i>Edit</a>
18 - <a href="{{ $book->getUrl() }}/sort" class="text-primary text-button"><i class="zmdi zmdi-sort"></i>Sort</a> 18 + @endif
19 + @if(userCan('book-update', $book) || userCan('restrictions-manage', $book) || userCan('book-delete', $book))
20 + <div dropdown class="dropdown-container">
21 + <a dropdown-toggle class="text-primary text-button"><i class="zmdi zmdi-more-vert"></i></a>
22 + <ul>
23 + @if(userCan('book-update', $book))
24 + <li><a href="{{ $book->getUrl() }}/sort" class="text-primary"><i class="zmdi zmdi-sort"></i>Sort</a></li>
19 @endif 25 @endif
20 @if(userCan('restrictions-manage', $book)) 26 @if(userCan('restrictions-manage', $book))
21 - <a href="{{$book->getUrl()}}/restrict" class="text-primary text-button"><i class="zmdi zmdi-lock-outline"></i>Restrict</a> 27 + <li><a href="{{$book->getUrl()}}/restrict" class="text-primary"><i class="zmdi zmdi-lock-outline"></i>Restrict</a></li>
22 @endif 28 @endif
23 @if(userCan('book-delete', $book)) 29 @if(userCan('book-delete', $book))
24 - <a href="{{ $book->getUrl() }}/delete" class="text-neg text-button"><i class="zmdi zmdi-delete"></i>Delete</a> 30 + <li><a href="{{ $book->getUrl() }}/delete" class="text-neg"><i class="zmdi zmdi-delete"></i>Delete</a></li>
31 + @endif
32 + </ul>
33 + </div>
25 @endif 34 @endif
26 </div> 35 </div>
27 </div> 36 </div>
...@@ -78,6 +87,15 @@ ...@@ -78,6 +87,15 @@
78 87
79 <div class="col-md-4 col-md-offset-1"> 88 <div class="col-md-4 col-md-offset-1">
80 <div class="margin-top large"></div> 89 <div class="margin-top large"></div>
90 + @if($book->restricted)
91 + <p class="text-muted">
92 + @if(userCan('restrictions-manage', $book))
93 + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a>
94 + @else
95 + <i class="zmdi zmdi-lock-outline"></i>Book Restricted
96 + @endif
97 + </p>
98 + @endif
81 <div class="search-box"> 99 <div class="search-box">
82 <form ng-submit="searchBook($event)"> 100 <form ng-submit="searchBook($event)">
83 <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book"> 101 <input ng-model="searchTerm" ng-change="checkSearchForm()" type="text" name="term" placeholder="Search This Book">
......
...@@ -2,6 +2,20 @@ ...@@ -2,6 +2,20 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{$chapter->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $chapter->book->getShortName() }}</a>
11 + <span class="sep">&raquo;</span>
12 + <a href="{{ $chapter->getUrl() }}" class="text-chapter text-button"><i class="zmdi zmdi-collection-bookmark"></i>{{$chapter->getShortName()}}</a>
13 + </div>
14 + </div>
15 + </div>
16 + </div>
17 + </div>
18 +
5 <div class="container" ng-non-bindable> 19 <div class="container" ng-non-bindable>
6 <h1>Chapter Restrictions</h1> 20 <h1>Chapter Restrictions</h1>
7 @include('form/restriction-form', ['model' => $chapter]) 21 @include('form/restriction-form', ['model' => $chapter])
......
...@@ -37,10 +37,10 @@ ...@@ -37,10 +37,10 @@
37 <h1>{{ $chapter->name }}</h1> 37 <h1>{{ $chapter->name }}</h1>
38 <p class="text-muted">{{ $chapter->description }}</p> 38 <p class="text-muted">{{ $chapter->description }}</p>
39 39
40 - @if(count($chapter->pages) > 0) 40 + @if(count($pages) > 0)
41 <div class="page-list"> 41 <div class="page-list">
42 <hr> 42 <hr>
43 - @foreach($chapter->pages as $page) 43 + @foreach($pages as $page)
44 @include('pages/list-item', ['page' => $page]) 44 @include('pages/list-item', ['page' => $page])
45 <hr> 45 <hr>
46 @endforeach 46 @endforeach
...@@ -63,6 +63,29 @@ ...@@ -63,6 +63,29 @@
63 </p> 63 </p>
64 </div> 64 </div>
65 <div class="col-md-3 col-md-offset-1"> 65 <div class="col-md-3 col-md-offset-1">
66 + <div class="margin-top large"></div>
67 + @if($book->restricted || $chapter->restricted)
68 + <div class="text-muted">
69 +
70 + @if($book->restricted)
71 + @if(userCan('restrictions-manage', $book))
72 + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book Restricted</a>
73 + @else
74 + <i class="zmdi zmdi-lock-outline"></i>Book Restricted
75 + @endif
76 + <br>
77 + @endif
78 +
79 + @if($chapter->restricted)
80 + @if(userCan('restrictions-manage', $chapter))
81 + <a href="{{ $chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter Restricted</a>
82 + @else
83 + <i class="zmdi zmdi-lock-outline"></i>Chapter Restricted
84 + @endif
85 + @endif
86 + </div>
87 + @endif
88 +
66 @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) 89 @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
67 </div> 90 </div>
68 </div> 91 </div>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 4
5 5
6 <div class="container"> 6 <div class="container">
7 - <h1 class="text-muted">Page Not Found</h1> 7 + <h1 class="text-muted">{{ $message or 'Page Not Found' }}</h1>
8 <p>Sorry, The page you were looking for could not be found.</p> 8 <p>Sorry, The page you were looking for could not be found.</p>
9 <a href="/" class="button">Return To Home</a> 9 <a href="/" class="button">Return To Home</a>
10 </div> 10 </div>
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
3 <input type="hidden" name="_method" value="PUT"> 3 <input type="hidden" name="_method" value="PUT">
4 4
5 <div class="form-group"> 5 <div class="form-group">
6 - @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this page?']) 6 + @include('form/checkbox', ['name' => 'restricted', 'label' => 'Restrict this ' . $model->getClassName()])
7 </div> 7 </div>
8 8
9 <table class="table"> 9 <table class="table">
...@@ -24,5 +24,6 @@ ...@@ -24,5 +24,6 @@
24 @endforeach 24 @endforeach
25 </table> 25 </table>
26 26
27 + <a href="{{ $model->getUrl() }}" class="button muted">Cancel</a>
27 <button type="submit" class="button pos">Save Restrictions</button> 28 <button type="submit" class="button pos">Save Restrictions</button>
28 </form> 29 </form>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -2,6 +2,27 @@ ...@@ -2,6 +2,27 @@
2 2
3 @section('content') 3 @section('content')
4 4
5 + <div class="faded-small toolbar">
6 + <div class="container">
7 + <div class="row">
8 + <div class="col-sm-12 faded">
9 + <div class="breadcrumbs">
10 + <a href="{{$page->book->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-book"></i>{{ $page->book->getShortName() }}</a>
11 + @if($page->hasChapter())
12 + <span class="sep">&raquo;</span>
13 + <a href="{{ $page->chapter->getUrl() }}" class="text-chapter text-button">
14 + <i class="zmdi zmdi-collection-bookmark"></i>
15 + {{$page->chapter->getShortName()}}
16 + </a>
17 + @endif
18 + <span class="sep">&raquo;</span>
19 + <a href="{{$page->getUrl()}}" class="text-book text-button"><i class="zmdi zmdi-file"></i>{{ $page->getShortName() }}</a>
20 + </div>
21 + </div>
22 + </div>
23 + </div>
24 + </div>
25 +
5 <div class="container" ng-non-bindable> 26 <div class="container" ng-non-bindable>
6 <h1>Page Restrictions</h1> 27 <h1>Page Restrictions</h1>
7 @include('form/restriction-form', ['model' => $page]) 28 @include('form/restriction-form', ['model' => $page])
......
...@@ -70,7 +70,38 @@ ...@@ -70,7 +70,38 @@
70 </div> 70 </div>
71 </div> 71 </div>
72 <div class="col-md-3 print-hidden"> 72 <div class="col-md-3 print-hidden">
73 + <div class="margin-top large"></div>
74 + @if($book->restricted || ($page->chapter && $page->chapter->restricted) || $page->restricted)
75 + <div class="text-muted">
73 76
77 + @if($book->restricted)
78 + @if(userCan('restrictions-manage', $book))
79 + <a href="{{ $book->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Book restricted</a>
80 + @else
81 + <i class="zmdi zmdi-lock-outline"></i>Book restricted
82 + @endif
83 + <br>
84 + @endif
85 +
86 + @if($page->chapter && $page->chapter->restricted)
87 + @if(userCan('restrictions-manage', $page->chapter))
88 + <a href="{{ $page->chapter->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Chapter restricted</a>
89 + @else
90 + <i class="zmdi zmdi-lock-outline"></i>Chapter restricted
91 + @endif
92 + <br>
93 + @endif
94 +
95 + @if($page->restricted)
96 + @if(userCan('restrictions-manage', $page))
97 + <a href="{{ $page->getUrl() }}/restrict"><i class="zmdi zmdi-lock-outline"></i>Page restricted</a>
98 + @else
99 + <i class="zmdi zmdi-lock-outline"></i>Page restricted
100 + @endif
101 + <br>
102 + @endif
103 + </div>
104 + @endif
74 @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree]) 105 @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
75 106
76 </div> 107 </div>
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
3 <div class="row"> 3 <div class="row">
4 4
5 <div class="col-md-6"> 5 <div class="col-md-6">
6 + <h3>Role Details</h3>
6 <div class="form-group"> 7 <div class="form-group">
7 <label for="name">Role Name</label> 8 <label for="name">Role Name</label>
8 @include('form/text', ['name' => 'display_name']) 9 @include('form/text', ['name' => 'display_name'])
...@@ -11,7 +12,7 @@ ...@@ -11,7 +12,7 @@
11 <label for="name">Short Role Description</label> 12 <label for="name">Short Role Description</label>
12 @include('form/text', ['name' => 'description']) 13 @include('form/text', ['name' => 'description'])
13 </div> 14 </div>
14 - <hr class="even"> 15 + <h3>System Permissions</h3>
15 <div class="row"> 16 <div class="row">
16 <div class="col-md-6"> 17 <div class="col-md-6">
17 <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label> 18 <label> @include('settings/roles/checkbox', ['permission' => 'users-manage']) Manage users</label>
...@@ -33,10 +34,17 @@ ...@@ -33,10 +34,17 @@
33 <div class="form-group"> 34 <div class="form-group">
34 <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label> 35 <label>@include('settings/roles/checkbox', ['permission' => 'settings-manage']) Manage app settings</label>
35 </div> 36 </div>
37 + <hr class="even">
36 38
37 </div> 39 </div>
38 40
39 <div class="col-md-6"> 41 <div class="col-md-6">
42 +
43 + <h3>Asset Permissions</h3>
44 + <p>
45 + These permissions control default access to the assets within the system. <br>
46 + Restrictions on Books, Chapters and Pages will override these permissions.
47 + </p>
40 <table class="table"> 48 <table class="table">
41 <tr> 49 <tr>
42 <th></th> 50 <th></th>
...@@ -104,4 +112,6 @@ ...@@ -104,4 +112,6 @@
104 </div> 112 </div>
105 113
106 </div> 114 </div>
115 +
116 +<a href="/settings/roles" class="button muted">Cancel</a>
107 <button type="submit" class="button pos">Save Role</button> 117 <button type="submit" class="button pos">Save Role</button>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
4 4
5 @include('settings/navbar', ['selected' => 'roles']) 5 @include('settings/navbar', ['selected' => 'roles'])
6 6
7 - <div class="container"> 7 + <div class="container small">
8 8
9 <h1>User Roles</h1> 9 <h1>User Roles</h1>
10 10
......
1 +<?php
2 +
3 +class RestrictionsTest extends TestCase
4 +{
5 + protected $user;
6 +
7 + public function setUp()
8 + {
9 + parent::setUp();
10 + $this->user = $this->getNewUser();
11 + }
12 +
13 + /**
14 + * Manually set some restrictions on an entity.
15 + * @param \BookStack\Entity $entity
16 + * @param $actions
17 + */
18 + protected function setEntityRestrictions(\BookStack\Entity $entity, $actions)
19 + {
20 + $entity->restricted = true;
21 + $entity->restrictions()->delete();
22 + $role = $this->user->roles->first();
23 + foreach ($actions as $action) {
24 + $entity->restrictions()->create([
25 + 'role_id' => $role->id,
26 + 'action' => strtolower($action)
27 + ]);
28 + }
29 + $entity->save();
30 + $entity->load('restrictions');
31 + }
32 +
33 + public function test_book_view_restriction()
34 + {
35 + $book = \BookStack\Book::first();
36 + $bookPage = $book->pages->first();
37 + $bookChapter = $book->chapters->first();
38 +
39 + $bookUrl = $book->getUrl();
40 + $this->actingAs($this->user)
41 + ->visit($bookUrl)
42 + ->seePageIs($bookUrl);
43 +
44 + $this->setEntityRestrictions($book, []);
45 +
46 + $this->forceVisit($bookUrl)
47 + ->see('Book not found');
48 + $this->forceVisit($bookPage->getUrl())
49 + ->see('Book not found');
50 + $this->forceVisit($bookChapter->getUrl())
51 + ->see('Book not found');
52 +
53 + $this->setEntityRestrictions($book, ['view']);
54 +
55 + $this->visit($bookUrl)
56 + ->see($book->name);
57 + $this->visit($bookPage->getUrl())
58 + ->see($bookPage->name);
59 + $this->visit($bookChapter->getUrl())
60 + ->see($bookChapter->name);
61 + }
62 +
63 + public function test_book_create_restriction()
64 + {
65 + $book = \BookStack\Book::first();
66 +
67 + $bookUrl = $book->getUrl();
68 + $this->actingAs($this->user)
69 + ->visit($bookUrl)
70 + ->seeInElement('.action-buttons', 'New Page')
71 + ->seeInElement('.action-buttons', 'New Chapter');
72 +
73 + $this->setEntityRestrictions($book, ['view', 'delete', 'update']);
74 +
75 + $this->forceVisit($bookUrl . '/chapter/create')
76 + ->see('You do not have permission')->seePageIs('/');
77 + $this->forceVisit($bookUrl . '/page/create')
78 + ->see('You do not have permission')->seePageIs('/');
79 + $this->visit($bookUrl)->dontSeeInElement('.action-buttons', 'New Page')
80 + ->dontSeeInElement('.action-buttons', 'New Chapter');
81 +
82 + $this->setEntityRestrictions($book, ['view', 'create']);
83 +
84 + $this->visit($bookUrl . '/chapter/create')
85 + ->type('test chapter', 'name')
86 + ->type('test description for chapter', 'description')
87 + ->press('Save Chapter')
88 + ->seePageIs($bookUrl . '/chapter/test-chapter');
89 + $this->visit($bookUrl . '/page/create')
90 + ->type('test page', 'name')
91 + ->type('test content', 'html')
92 + ->press('Save Page')
93 + ->seePageIs($bookUrl . '/page/test-page');
94 + $this->visit($bookUrl)->seeInElement('.action-buttons', 'New Page')
95 + ->seeInElement('.action-buttons', 'New Chapter');
96 + }
97 +
98 + public function test_book_update_restriction()
99 + {
100 + $book = \BookStack\Book::first();
101 + $bookPage = $book->pages->first();
102 + $bookChapter = $book->chapters->first();
103 +
104 + $bookUrl = $book->getUrl();
105 + $this->actingAs($this->user)
106 + ->visit($bookUrl . '/edit')
107 + ->see('Edit Book');
108 +
109 + $this->setEntityRestrictions($book, ['view', 'delete']);
110 +
111 + $this->forceVisit($bookUrl . '/edit')
112 + ->see('You do not have permission')->seePageIs('/');
113 + $this->forceVisit($bookPage->getUrl() . '/edit')
114 + ->see('You do not have permission')->seePageIs('/');
115 + $this->forceVisit($bookChapter->getUrl() . '/edit')
116 + ->see('You do not have permission')->seePageIs('/');
117 +
118 + $this->setEntityRestrictions($book, ['view', 'update']);
119 +
120 + $this->visit($bookUrl . '/edit')
121 + ->seePageIs($bookUrl . '/edit');
122 + $this->visit($bookPage->getUrl() . '/edit')
123 + ->seePageIs($bookPage->getUrl() . '/edit');
124 + $this->visit($bookChapter->getUrl() . '/edit')
125 + ->see('Edit Chapter');
126 + }
127 +
128 + public function test_book_delete_restriction()
129 + {
130 + $book = \BookStack\Book::first();
131 + $bookPage = $book->pages->first();
132 + $bookChapter = $book->chapters->first();
133 +
134 + $bookUrl = $book->getUrl();
135 + $this->actingAs($this->user)
136 + ->visit($bookUrl . '/delete')
137 + ->see('Delete Book');
138 +
139 + $this->setEntityRestrictions($book, ['view', 'update']);
140 +
141 + $this->forceVisit($bookUrl . '/delete')
142 + ->see('You do not have permission')->seePageIs('/');
143 + $this->forceVisit($bookPage->getUrl() . '/delete')
144 + ->see('You do not have permission')->seePageIs('/');
145 + $this->forceVisit($bookChapter->getUrl() . '/delete')
146 + ->see('You do not have permission')->seePageIs('/');
147 +
148 + $this->setEntityRestrictions($book, ['view', 'delete']);
149 +
150 + $this->visit($bookUrl . '/delete')
151 + ->seePageIs($bookUrl . '/delete')->see('Delete Book');
152 + $this->visit($bookPage->getUrl() . '/delete')
153 + ->seePageIs($bookPage->getUrl() . '/delete')->see('Delete Page');
154 + $this->visit($bookChapter->getUrl() . '/delete')
155 + ->see('Delete Chapter');
156 + }
157 +
158 + public function test_chapter_view_restriction()
159 + {
160 + $chapter = \BookStack\Chapter::first();
161 + $chapterPage = $chapter->pages->first();
162 +
163 + $chapterUrl = $chapter->getUrl();
164 + $this->actingAs($this->user)
165 + ->visit($chapterUrl)
166 + ->seePageIs($chapterUrl);
167 +
168 + $this->setEntityRestrictions($chapter, []);
169 +
170 + $this->forceVisit($chapterUrl)
171 + ->see('Chapter not found');
172 + $this->forceVisit($chapterPage->getUrl())
173 + ->see('Page not found');
174 +
175 + $this->setEntityRestrictions($chapter, ['view']);
176 +
177 + $this->visit($chapterUrl)
178 + ->see($chapter->name);
179 + $this->visit($chapterPage->getUrl())
180 + ->see($chapterPage->name);
181 + }
182 +
183 + public function test_chapter_create_restriction()
184 + {
185 + $chapter = \BookStack\Chapter::first();
186 +
187 + $chapterUrl = $chapter->getUrl();
188 + $this->actingAs($this->user)
189 + ->visit($chapterUrl)
190 + ->seeInElement('.action-buttons', 'New Page');
191 +
192 + $this->setEntityRestrictions($chapter, ['view', 'delete', 'update']);
193 +
194 + $this->forceVisit($chapterUrl . '/create-page')
195 + ->see('You do not have permission')->seePageIs('/');
196 + $this->visit($chapterUrl)->dontSeeInElement('.action-buttons', 'New Page');
197 +
198 + $this->setEntityRestrictions($chapter, ['view', 'create']);
199 +
200 +
201 + $this->visit($chapterUrl . '/create-page')
202 + ->type('test page', 'name')
203 + ->type('test content', 'html')
204 + ->press('Save Page')
205 + ->seePageIs($chapter->book->getUrl() . '/page/test-page');
206 + $this->visit($chapterUrl)->seeInElement('.action-buttons', 'New Page');
207 + }
208 +
209 + public function test_chapter_update_restriction()
210 + {
211 + $chapter = \BookStack\Chapter::first();
212 + $chapterPage = $chapter->pages->first();
213 +
214 + $chapterUrl = $chapter->getUrl();
215 + $this->actingAs($this->user)
216 + ->visit($chapterUrl . '/edit')
217 + ->see('Edit Chapter');
218 +
219 + $this->setEntityRestrictions($chapter, ['view', 'delete']);
220 +
221 + $this->forceVisit($chapterUrl . '/edit')
222 + ->see('You do not have permission')->seePageIs('/');
223 + $this->forceVisit($chapterPage->getUrl() . '/edit')
224 + ->see('You do not have permission')->seePageIs('/');
225 +
226 + $this->setEntityRestrictions($chapter, ['view', 'update']);
227 +
228 + $this->visit($chapterUrl . '/edit')
229 + ->seePageIs($chapterUrl . '/edit')->see('Edit Chapter');
230 + $this->visit($chapterPage->getUrl() . '/edit')
231 + ->seePageIs($chapterPage->getUrl() . '/edit');
232 + }
233 +
234 + public function test_chapter_delete_restriction()
235 + {
236 + $chapter = \BookStack\Chapter::first();
237 + $chapterPage = $chapter->pages->first();
238 +
239 + $chapterUrl = $chapter->getUrl();
240 + $this->actingAs($this->user)
241 + ->visit($chapterUrl . '/delete')
242 + ->see('Delete Chapter');
243 +
244 + $this->setEntityRestrictions($chapter, ['view', 'update']);
245 +
246 + $this->forceVisit($chapterUrl . '/delete')
247 + ->see('You do not have permission')->seePageIs('/');
248 + $this->forceVisit($chapterPage->getUrl() . '/delete')
249 + ->see('You do not have permission')->seePageIs('/');
250 +
251 + $this->setEntityRestrictions($chapter, ['view', 'delete']);
252 +
253 + $this->visit($chapterUrl . '/delete')
254 + ->seePageIs($chapterUrl . '/delete')->see('Delete Chapter');
255 + $this->visit($chapterPage->getUrl() . '/delete')
256 + ->seePageIs($chapterPage->getUrl() . '/delete')->see('Delete Page');
257 + }
258 +
259 + public function test_page_view_restriction()
260 + {
261 + $page = \BookStack\Page::first();
262 +
263 + $pageUrl = $page->getUrl();
264 + $this->actingAs($this->user)
265 + ->visit($pageUrl)
266 + ->seePageIs($pageUrl);
267 +
268 + $this->setEntityRestrictions($page, ['update', 'delete']);
269 +
270 + $this->forceVisit($pageUrl)
271 + ->see('Page not found');
272 +
273 + $this->setEntityRestrictions($page, ['view']);
274 +
275 + $this->visit($pageUrl)
276 + ->see($page->name);
277 + }
278 +
279 + public function test_page_update_restriction()
280 + {
281 + $page = \BookStack\Chapter::first();
282 +
283 + $pageUrl = $page->getUrl();
284 + $this->actingAs($this->user)
285 + ->visit($pageUrl . '/edit')
286 + ->seeInField('name', $page->name);
287 +
288 + $this->setEntityRestrictions($page, ['view', 'delete']);
289 +
290 + $this->forceVisit($pageUrl . '/edit')
291 + ->see('You do not have permission')->seePageIs('/');
292 +
293 + $this->setEntityRestrictions($page, ['view', 'update']);
294 +
295 + $this->visit($pageUrl . '/edit')
296 + ->seePageIs($pageUrl . '/edit')->seeInField('name', $page->name);
297 + }
298 +
299 + public function test_page_delete_restriction()
300 + {
301 + $page = \BookStack\Page::first();
302 +
303 + $pageUrl = $page->getUrl();
304 + $this->actingAs($this->user)
305 + ->visit($pageUrl . '/delete')
306 + ->see('Delete Page');
307 +
308 + $this->setEntityRestrictions($page, ['view', 'update']);
309 +
310 + $this->forceVisit($pageUrl . '/delete')
311 + ->see('You do not have permission')->seePageIs('/');
312 +
313 + $this->setEntityRestrictions($page, ['view', 'delete']);
314 +
315 + $this->visit($pageUrl . '/delete')
316 + ->seePageIs($pageUrl . '/delete')->see('Delete Page');
317 + }
318 +
319 + public function test_book_restriction_form()
320 + {
321 + $book = \BookStack\Book::first();
322 + $this->asAdmin()->visit($book->getUrl() . '/restrict')
323 + ->see('Book Restrictions')
324 + ->check('restricted')
325 + ->check('restrictions[2][view]')
326 + ->press('Save Restrictions')
327 + ->seeInDatabase('books', ['id' => $book->id, 'restricted' => true])
328 + ->seeInDatabase('restrictions', [
329 + 'restrictable_id' => $book->id,
330 + 'restrictable_type' => 'BookStack\Book',
331 + 'role_id' => '2',
332 + 'action' => 'view'
333 + ]);
334 + }
335 +
336 + public function test_chapter_restriction_form()
337 + {
338 + $chapter = \BookStack\Chapter::first();
339 + $this->asAdmin()->visit($chapter->getUrl() . '/restrict')
340 + ->see('Chapter Restrictions')
341 + ->check('restricted')
342 + ->check('restrictions[2][update]')
343 + ->press('Save Restrictions')
344 + ->seeInDatabase('chapters', ['id' => $chapter->id, 'restricted' => true])
345 + ->seeInDatabase('restrictions', [
346 + 'restrictable_id' => $chapter->id,
347 + 'restrictable_type' => 'BookStack\Chapter',
348 + 'role_id' => '2',
349 + 'action' => 'update'
350 + ]);
351 + }
352 +
353 + public function test_page_restriction_form()
354 + {
355 + $page = \BookStack\Page::first();
356 + $this->asAdmin()->visit($page->getUrl() . '/restrict')
357 + ->see('Page Restrictions')
358 + ->check('restricted')
359 + ->check('restrictions[2][delete]')
360 + ->press('Save Restrictions')
361 + ->seeInDatabase('pages', ['id' => $page->id, 'restricted' => true])
362 + ->seeInDatabase('restrictions', [
363 + 'restrictable_id' => $page->id,
364 + 'restrictable_type' => 'BookStack\Page',
365 + 'role_id' => '2',
366 + 'action' => 'delete'
367 + ]);
368 + }
369 +
370 + public function test_restricted_pages_not_visible_in_book_navigation_on_pages()
371 + {
372 + $chapter = \BookStack\Chapter::first();
373 + $page = $chapter->pages->first();
374 + $page2 = $chapter->pages[2];
375 +
376 + $this->setEntityRestrictions($page, []);
377 +
378 + $this->actingAs($this->user)
379 + ->visit($page2->getUrl())
380 + ->dontSeeInElement('.sidebar-page-list', $page->name);
381 + }
382 +
383 + public function test_restricted_pages_not_visible_in_book_navigation_on_chapters()
384 + {
385 + $chapter = \BookStack\Chapter::first();
386 + $page = $chapter->pages->first();
387 +
388 + $this->setEntityRestrictions($page, []);
389 +
390 + $this->actingAs($this->user)
391 + ->visit($chapter->getUrl())
392 + ->dontSeeInElement('.sidebar-page-list', $page->name);
393 + }
394 +
395 + public function test_restricted_pages_not_visible_on_chapter_pages()
396 + {
397 + $chapter = \BookStack\Chapter::first();
398 + $page = $chapter->pages->first();
399 +
400 + $this->setEntityRestrictions($page, []);
401 +
402 + $this->actingAs($this->user)
403 + ->visit($chapter->getUrl())
404 + ->dontSee($page->name);
405 + }
406 +
407 +}
1 <?php 1 <?php
2 2
3 use Illuminate\Foundation\Testing\DatabaseTransactions; 3 use Illuminate\Foundation\Testing\DatabaseTransactions;
4 +use Symfony\Component\DomCrawler\Crawler;
4 5
5 class TestCase extends Illuminate\Foundation\Testing\TestCase 6 class TestCase extends Illuminate\Foundation\Testing\TestCase
6 { 7 {
...@@ -123,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -123,6 +124,40 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
123 } 124 }
124 125
125 /** 126 /**
127 + * Assert that the current page matches a given URI.
128 + *
129 + * @param string $uri
130 + * @return $this
131 + */
132 + protected function seePageUrlIs($uri)
133 + {
134 + $this->assertEquals(
135 + $uri, $this->currentUri, "Did not land on expected page [{$uri}].\n"
136 + );
137 +
138 + return $this;
139 + }
140 +
141 + /**
142 + * Do a forced visit that does not error out on exception.
143 + * @param string $uri
144 + * @param array $parameters
145 + * @param array $cookies
146 + * @param array $files
147 + * @return $this
148 + */
149 + protected function forceVisit($uri, $parameters = [], $cookies = [], $files = [])
150 + {
151 + $method = 'GET';
152 + $uri = $this->prepareUrlForRequest($uri);
153 + $this->call($method, $uri, $parameters, $cookies, $files);
154 + $this->clearInputs()->followRedirects();
155 + $this->currentUri = $this->app->make('request')->fullUrl();
156 + $this->crawler = new Crawler($this->response->getContent(), $uri);
157 + return $this;
158 + }
159 +
160 + /**
126 * Click the text within the selected element. 161 * Click the text within the selected element.
127 * @param $parentElement 162 * @param $parentElement
128 * @param $linkText 163 * @param $linkText
......