Dan Brown

Rolled out new permissions system throughout application

...@@ -70,7 +70,20 @@ abstract class Entity extends Ownable ...@@ -70,7 +70,20 @@ abstract class Entity extends Ownable
70 */ 70 */
71 public function hasRestriction($role_id, $action) 71 public function hasRestriction($role_id, $action)
72 { 72 {
73 - return $this->restrictions->where('role_id', $role_id)->where('action', $action)->count() > 0; 73 + return $this->restrictions()->where('role_id', '=', $role_id)
74 + ->where('action', '=', $action)->count() > 0;
75 + }
76 +
77 + /**
78 + * Check if this entity has live (active) restrictions in place.
79 + * @param $role_id
80 + * @param $action
81 + * @return bool
82 + */
83 + public function hasActiveRestriction($role_id, $action)
84 + {
85 + return $this->restricted && $this->restrictions()
86 + ->where('role_id', '=', $role_id)->where('action', '=', $action)->count() > 0;
74 } 87 }
75 88
76 /** 89 /**
......
...@@ -71,11 +71,7 @@ class BookController extends Controller ...@@ -71,11 +71,7 @@ class BookController extends Controller
71 'name' => 'required|string|max:255', 71 'name' => 'required|string|max:255',
72 'description' => 'string|max:1000' 72 'description' => 'string|max:1000'
73 ]); 73 ]);
74 - $book = $this->bookRepo->newFromInput($request->all()); 74 + $book = $this->bookRepo->createFromInput($request->all());
75 - $book->slug = $this->bookRepo->findSuitableSlug($book->name);
76 - $book->created_by = Auth::user()->id;
77 - $book->updated_by = Auth::user()->id;
78 - $book->save();
79 Activity::add($book, 'book_create', $book->id); 75 Activity::add($book, 'book_create', $book->id);
80 return redirect($book->getUrl()); 76 return redirect($book->getUrl());
81 } 77 }
...@@ -122,10 +118,7 @@ class BookController extends Controller ...@@ -122,10 +118,7 @@ class BookController extends Controller
122 'name' => 'required|string|max:255', 118 'name' => 'required|string|max:255',
123 'description' => 'string|max:1000' 119 'description' => 'string|max:1000'
124 ]); 120 ]);
125 - $book->fill($request->all()); 121 + $book = $this->bookRepo->updateFromInput($book, $request->all());
126 - $book->slug = $this->bookRepo->findSuitableSlug($book->name, $book->id);
127 - $book->updated_by = Auth::user()->id;
128 - $book->save();
129 Activity::add($book, 'book_update', $book->id); 122 Activity::add($book, 'book_update', $book->id);
130 return redirect($book->getUrl()); 123 return redirect($book->getUrl());
131 } 124 }
...@@ -210,6 +203,7 @@ class BookController extends Controller ...@@ -210,6 +203,7 @@ class BookController extends Controller
210 // Add activity for books 203 // Add activity for books
211 foreach ($sortedBooks as $bookId) { 204 foreach ($sortedBooks as $bookId) {
212 $updatedBook = $this->bookRepo->getById($bookId); 205 $updatedBook = $this->bookRepo->getById($bookId);
206 + $this->bookRepo->updateBookPermissions($updatedBook);
213 Activity::add($updatedBook, 'book_sort', $updatedBook->id); 207 Activity::add($updatedBook, 'book_sort', $updatedBook->id);
214 } 208 }
215 209
...@@ -227,7 +221,7 @@ class BookController extends Controller ...@@ -227,7 +221,7 @@ class BookController extends Controller
227 $this->checkOwnablePermission('book-delete', $book); 221 $this->checkOwnablePermission('book-delete', $book);
228 Activity::addMessage('book_delete', 0, $book->name); 222 Activity::addMessage('book_delete', 0, $book->name);
229 Activity::removeEntity($book); 223 Activity::removeEntity($book);
230 - $this->bookRepo->destroyBySlug($bookSlug); 224 + $this->bookRepo->destroy($book);
231 return redirect('/books'); 225 return redirect('/books');
232 } 226 }
233 227
......
...@@ -57,12 +57,9 @@ class ChapterController extends Controller ...@@ -57,12 +57,9 @@ class ChapterController extends Controller
57 $book = $this->bookRepo->getBySlug($bookSlug); 57 $book = $this->bookRepo->getBySlug($bookSlug);
58 $this->checkOwnablePermission('chapter-create', $book); 58 $this->checkOwnablePermission('chapter-create', $book);
59 59
60 - $chapter = $this->chapterRepo->newFromInput($request->all()); 60 + $input = $request->all();
61 - $chapter->slug = $this->chapterRepo->findSuitableSlug($chapter->name, $book->id); 61 + $input['priority'] = $this->bookRepo->getNewPriority($book);
62 - $chapter->priority = $this->bookRepo->getNewPriority($book); 62 + $chapter = $this->chapterRepo->createFromInput($request->all(), $book);
63 - $chapter->created_by = auth()->user()->id;
64 - $chapter->updated_by = auth()->user()->id;
65 - $book->chapters()->save($chapter);
66 Activity::add($chapter, 'chapter_create', $book->id); 63 Activity::add($chapter, 'chapter_create', $book->id);
67 return redirect($chapter->getUrl()); 64 return redirect($chapter->getUrl());
68 } 65 }
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 use BookStack\Exceptions\PermissionsException; 3 use BookStack\Exceptions\PermissionsException;
4 use BookStack\Repos\PermissionsRepo; 4 use BookStack\Repos\PermissionsRepo;
5 +use BookStack\Services\RestrictionService;
5 use Illuminate\Http\Request; 6 use Illuminate\Http\Request;
6 use BookStack\Http\Requests; 7 use BookStack\Http\Requests;
7 8
......
1 -<?php 1 +<?php namespace BookStack;
2 -
3 -namespace BookStack;
4 2
5 use Illuminate\Database\Eloquent\Model; 3 use Illuminate\Database\Eloquent\Model;
6 4
...@@ -16,7 +14,7 @@ class Permission extends Model ...@@ -16,7 +14,7 @@ class Permission extends Model
16 14
17 /** 15 /**
18 * Get the permission object by name. 16 * Get the permission object by name.
19 - * @param $roleName 17 + * @param $name
20 * @return mixed 18 * @return mixed
21 */ 19 */
22 public static function getByName($name) 20 public static function getByName($name)
......
1 <?php namespace BookStack\Repos; 1 <?php namespace BookStack\Repos;
2 2
3 +use Alpha\B;
3 use BookStack\Exceptions\NotFoundException; 4 use BookStack\Exceptions\NotFoundException;
4 use Illuminate\Support\Str; 5 use Illuminate\Support\Str;
5 use BookStack\Book; 6 use BookStack\Book;
...@@ -123,21 +124,43 @@ class BookRepo extends EntityRepo ...@@ -123,21 +124,43 @@ class BookRepo extends EntityRepo
123 124
124 /** 125 /**
125 * Get a new book instance from request input. 126 * Get a new book instance from request input.
127 + * @param array $input
128 + * @return Book
129 + */
130 + public function createFromInput($input)
131 + {
132 + $book = $this->book->newInstance($input);
133 + $book->slug = $this->findSuitableSlug($book->name);
134 + $book->created_by = auth()->user()->id;
135 + $book->updated_by = auth()->user()->id;
136 + $book->save();
137 + $this->restrictionService->buildEntityPermissionsForEntity($book);
138 + return $book;
139 + }
140 +
141 + /**
142 + * Update the given book from user input.
143 + * @param Book $book
126 * @param $input 144 * @param $input
127 * @return Book 145 * @return Book
128 */ 146 */
129 - public function newFromInput($input) 147 + public function updateFromInput(Book $book, $input)
130 { 148 {
131 - return $this->book->newInstance($input); 149 + $book->fill($input);
150 + $book->slug = $this->findSuitableSlug($book->name, $book->id);
151 + $book->updated_by = auth()->user()->id;
152 + $book->save();
153 + $this->restrictionService->buildEntityPermissionsForEntity($book);
154 + return $book;
132 } 155 }
133 156
134 /** 157 /**
135 - * Destroy a book identified by the given slug. 158 + * Destroy the given book.
136 - * @param $bookSlug 159 + * @param Book $book
160 + * @throws \Exception
137 */ 161 */
138 - public function destroyBySlug($bookSlug) 162 + public function destroy(Book $book)
139 { 163 {
140 - $book = $this->getBySlug($bookSlug);
141 foreach ($book->pages as $page) { 164 foreach ($book->pages as $page) {
142 $this->pageRepo->destroy($page); 165 $this->pageRepo->destroy($page);
143 } 166 }
...@@ -146,10 +169,20 @@ class BookRepo extends EntityRepo ...@@ -146,10 +169,20 @@ class BookRepo extends EntityRepo
146 } 169 }
147 $book->views()->delete(); 170 $book->views()->delete();
148 $book->restrictions()->delete(); 171 $book->restrictions()->delete();
172 + $this->restrictionService->deleteEntityPermissionsForEntity($book);
149 $book->delete(); 173 $book->delete();
150 } 174 }
151 175
152 /** 176 /**
177 + * Alias method to update the book permissions in the RestrictionService.
178 + * @param Book $book
179 + */
180 + public function updateBookPermissions(Book $book)
181 + {
182 + $this->restrictionService->buildEntityPermissionsForEntity($book);
183 + }
184 +
185 + /**
153 * Get the next child element priority. 186 * Get the next child element priority.
154 * @param Book $book 187 * @param Book $book
155 * @return int 188 * @return int
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
2 2
3 3
4 use Activity; 4 use Activity;
5 +use BookStack\Book;
5 use BookStack\Exceptions\NotFoundException; 6 use BookStack\Exceptions\NotFoundException;
6 use Illuminate\Support\Str; 7 use Illuminate\Support\Str;
7 use BookStack\Chapter; 8 use BookStack\Chapter;
...@@ -78,11 +79,18 @@ class ChapterRepo extends EntityRepo ...@@ -78,11 +79,18 @@ class ChapterRepo extends EntityRepo
78 /** 79 /**
79 * Create a new chapter from request input. 80 * Create a new chapter from request input.
80 * @param $input 81 * @param $input
81 - * @return $this 82 + * @param Book $book
83 + * @return Chapter
82 */ 84 */
83 - public function newFromInput($input) 85 + public function createFromInput($input, Book $book)
84 { 86 {
85 - return $this->chapter->fill($input); 87 + $chapter = $this->chapter->newInstance($input);
88 + $chapter->slug = $this->findSuitableSlug($chapter->name, $book->id);
89 + $chapter->created_by = auth()->user()->id;
90 + $chapter->updated_by = auth()->user()->id;
91 + $chapter = $book->chapters()->save($chapter);
92 + $this->restrictionService->buildEntityPermissionsForEntity($chapter);
93 + return $chapter;
86 } 94 }
87 95
88 /** 96 /**
...@@ -100,6 +108,7 @@ class ChapterRepo extends EntityRepo ...@@ -100,6 +108,7 @@ class ChapterRepo extends EntityRepo
100 Activity::removeEntity($chapter); 108 Activity::removeEntity($chapter);
101 $chapter->views()->delete(); 109 $chapter->views()->delete();
102 $chapter->restrictions()->delete(); 110 $chapter->restrictions()->delete();
111 + $this->restrictionService->deleteEntityPermissionsForEntity($chapter);
103 $chapter->delete(); 112 $chapter->delete();
104 } 113 }
105 114
......
...@@ -151,6 +151,7 @@ class EntityRepo ...@@ -151,6 +151,7 @@ class EntityRepo
151 } 151 }
152 } 152 }
153 $entity->save(); 153 $entity->save();
154 + $this->restrictionService->buildEntityPermissionsForEntity($entity);
154 } 155 }
155 156
156 /** 157 /**
......
...@@ -168,6 +168,7 @@ class PageRepo extends EntityRepo ...@@ -168,6 +168,7 @@ class PageRepo extends EntityRepo
168 if ($chapter) $page->chapter_id = $chapter->id; 168 if ($chapter) $page->chapter_id = $chapter->id;
169 169
170 $book->pages()->save($page); 170 $book->pages()->save($page);
171 + $this->restrictionService->buildEntityPermissionsForEntity($page);
171 return $page; 172 return $page;
172 } 173 }
173 174
...@@ -583,6 +584,7 @@ class PageRepo extends EntityRepo ...@@ -583,6 +584,7 @@ class PageRepo extends EntityRepo
583 $page->views()->delete(); 584 $page->views()->delete();
584 $page->revisions()->delete(); 585 $page->revisions()->delete();
585 $page->restrictions()->delete(); 586 $page->restrictions()->delete();
587 + $this->restrictionService->deleteEntityPermissionsForEntity($page);
586 $page->delete(); 588 $page->delete();
587 } 589 }
588 590
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
4 use BookStack\Exceptions\PermissionsException; 4 use BookStack\Exceptions\PermissionsException;
5 use BookStack\Permission; 5 use BookStack\Permission;
6 use BookStack\Role; 6 use BookStack\Role;
7 +use BookStack\Services\RestrictionService;
7 use Setting; 8 use Setting;
8 9
9 class PermissionsRepo 10 class PermissionsRepo
...@@ -11,16 +12,19 @@ class PermissionsRepo ...@@ -11,16 +12,19 @@ class PermissionsRepo
11 12
12 protected $permission; 13 protected $permission;
13 protected $role; 14 protected $role;
15 + protected $restrictionService;
14 16
15 /** 17 /**
16 * PermissionsRepo constructor. 18 * PermissionsRepo constructor.
17 - * @param $permission 19 + * @param Permission $permission
18 - * @param $role 20 + * @param Role $role
21 + * @param RestrictionService $restrictionService
19 */ 22 */
20 - public function __construct(Permission $permission, Role $role) 23 + public function __construct(Permission $permission, Role $role, RestrictionService $restrictionService)
21 { 24 {
22 $this->permission = $permission; 25 $this->permission = $permission;
23 $this->role = $role; 26 $this->role = $role;
27 + $this->restrictionService = $restrictionService;
24 } 28 }
25 29
26 /** 30 /**
...@@ -69,6 +73,7 @@ class PermissionsRepo ...@@ -69,6 +73,7 @@ class PermissionsRepo
69 73
70 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : []; 74 $permissions = isset($roleData['permissions']) ? array_keys($roleData['permissions']) : [];
71 $this->assignRolePermissions($role, $permissions); 75 $this->assignRolePermissions($role, $permissions);
76 + $this->restrictionService->buildEntityPermissionForRole($role);
72 return $role; 77 return $role;
73 } 78 }
74 79
...@@ -91,6 +96,7 @@ class PermissionsRepo ...@@ -91,6 +96,7 @@ class PermissionsRepo
91 96
92 $role->fill($roleData); 97 $role->fill($roleData);
93 $role->save(); 98 $role->save();
99 + $this->restrictionService->buildEntityPermissionForRole($role);
94 } 100 }
95 101
96 /** 102 /**
...@@ -136,6 +142,7 @@ class PermissionsRepo ...@@ -136,6 +142,7 @@ class PermissionsRepo
136 } 142 }
137 } 143 }
138 144
145 + $this->restrictionService->deleteEntityPermissionsForRole($role);
139 $role->delete(); 146 $role->delete();
140 } 147 }
141 148
......
...@@ -18,6 +18,15 @@ class Role extends Model ...@@ -18,6 +18,15 @@ class Role extends Model
18 } 18 }
19 19
20 /** 20 /**
21 + * Get all related entity permissions.
22 + * @return \Illuminate\Database\Eloquent\Relations\HasMany
23 + */
24 + public function entityPermissions()
25 + {
26 + return $this->hasMany(EntityPermission::class);
27 + }
28 +
29 + /**
21 * The permissions that belong to the role. 30 * The permissions that belong to the role.
22 */ 31 */
23 public function permissions() 32 public function permissions()
......
...@@ -105,8 +105,16 @@ class ActivityService ...@@ -105,8 +105,16 @@ class ActivityService
105 */ 105 */
106 public function entityActivity($entity, $count = 20, $page = 0) 106 public function entityActivity($entity, $count = 20, $page = 0)
107 { 107 {
108 - $activity = $entity->hasMany('BookStack\Activity')->orderBy('created_at', 'desc') 108 + if ($entity->isA('book')) {
109 - ->skip($count * $page)->take($count)->get(); 109 + $query = $this->activity->where('book_id', '=', $entity->id);
110 + } else {
111 + $query = $this->activity->where('entity_type', '=', get_class($entity))
112 + ->where('entity_id', '=', $entity->id);
113 + }
114 +
115 + $activity = $this->restrictionService
116 + ->filterRestrictedEntityRelations($query, 'activities', 'entity_id', 'entity_type')
117 + ->orderBy('created_at', 'desc')->skip($count * $page)->take($count)->get();
110 118
111 return $this->filterSimilar($activity); 119 return $this->filterSimilar($activity);
112 } 120 }
......
...@@ -23,11 +23,14 @@ class RestrictionService ...@@ -23,11 +23,14 @@ class RestrictionService
23 protected $entityPermission; 23 protected $entityPermission;
24 protected $role; 24 protected $role;
25 25
26 + /**
27 + * The actions that have permissions attached throughout the application.
28 + * @var array
29 + */
26 protected $actions = ['view', 'create', 'update', 'delete']; 30 protected $actions = ['view', 'create', 'update', 'delete'];
27 31
28 /** 32 /**
29 * RestrictionService constructor. 33 * RestrictionService constructor.
30 - * TODO - Handle events when roles or entities change.
31 * @param EntityPermission $entityPermission 34 * @param EntityPermission $entityPermission
32 * @param Book $book 35 * @param Book $book
33 * @param Chapter $chapter 36 * @param Chapter $chapter
...@@ -74,6 +77,92 @@ class RestrictionService ...@@ -74,6 +77,92 @@ class RestrictionService
74 } 77 }
75 78
76 /** 79 /**
80 + * Create the entity permissions for a particular entity.
81 + * @param Entity $entity
82 + */
83 + public function buildEntityPermissionsForEntity(Entity $entity)
84 + {
85 + $roles = $this->role->load('permissions')->all();
86 + $entities = collect([$entity]);
87 +
88 + if ($entity->isA('book')) {
89 + $entities = $entities->merge($entity->chapters);
90 + $entities = $entities->merge($entity->pages);
91 + } elseif ($entity->isA('chapter')) {
92 + $entities = $entities->merge($entity->pages);
93 + }
94 +
95 + $this->deleteManyEntityPermissionsForEntities($entities);
96 + $this->createManyEntityPermissions($entities, $roles);
97 + }
98 +
99 + /**
100 + * Build the entity permissions for a particular role.
101 + * @param Role $role
102 + */
103 + public function buildEntityPermissionForRole(Role $role)
104 + {
105 + $roles = collect([$role]);
106 +
107 + $this->deleteManyEntityPermissionsForRoles($roles);
108 +
109 + // Chunk through all books
110 + $this->book->chunk(500, function ($books) use ($roles) {
111 + $this->createManyEntityPermissions($books, $roles);
112 + });
113 +
114 + // Chunk through all chapters
115 + $this->chapter->with('book')->chunk(500, function ($books) use ($roles) {
116 + $this->createManyEntityPermissions($books, $roles);
117 + });
118 +
119 + // Chunk through all pages
120 + $this->page->with('book', 'chapter')->chunk(500, function ($books) use ($roles) {
121 + $this->createManyEntityPermissions($books, $roles);
122 + });
123 + }
124 +
125 + /**
126 + * Delete the entity permissions attached to a particular role.
127 + * @param Role $role
128 + */
129 + public function deleteEntityPermissionsForRole(Role $role)
130 + {
131 + $this->deleteManyEntityPermissionsForRoles([$role]);
132 + }
133 +
134 + /**
135 + * Delete all of the entity permissions for a list of entities.
136 + * @param Role[] $roles
137 + */
138 + protected function deleteManyEntityPermissionsForRoles($roles)
139 + {
140 + foreach ($roles as $role) {
141 + $role->entityPermissions()->delete();
142 + }
143 + }
144 +
145 + /**
146 + * Delete the entity permissions for a particular entity.
147 + * @param Entity $entity
148 + */
149 + public function deleteEntityPermissionsForEntity(Entity $entity)
150 + {
151 + $this->deleteManyEntityPermissionsForEntities([$entity]);
152 + }
153 +
154 + /**
155 + * Delete all of the entity permissions for a list of entities.
156 + * @param Entity[] $entities
157 + */
158 + protected function deleteManyEntityPermissionsForEntities($entities)
159 + {
160 + foreach ($entities as $entity) {
161 + $entity->permissions()->delete();
162 + }
163 + }
164 +
165 + /**
77 * Create & Save entity permissions for many entities and permissions. 166 * Create & Save entity permissions for many entities and permissions.
78 * @param Collection $entities 167 * @param Collection $entities
79 * @param Collection $roles 168 * @param Collection $roles
...@@ -88,10 +177,18 @@ class RestrictionService ...@@ -88,10 +177,18 @@ class RestrictionService
88 } 177 }
89 } 178 }
90 } 179 }
180 + \Log::info(collect($entityPermissions)->where('entity_id', 1)->where('entity_type', 'BookStack\\Page')->where('role_id', 2)->all());
91 $this->entityPermission->insert($entityPermissions); 181 $this->entityPermission->insert($entityPermissions);
92 } 182 }
93 183
94 - 184 + /**
185 + * Create entity permission data for an entity and role
186 + * for a particular action.
187 + * @param Entity $entity
188 + * @param Role $role
189 + * @param $action
190 + * @return array
191 + */
95 protected function createEntityPermissionData(Entity $entity, Role $role, $action) 192 protected function createEntityPermissionData(Entity $entity, Role $role, $action)
96 { 193 {
97 $permissionPrefix = $entity->getType() . '-' . $action; 194 $permissionPrefix = $entity->getType() . '-' . $action;
...@@ -103,29 +200,39 @@ class RestrictionService ...@@ -103,29 +200,39 @@ class RestrictionService
103 if (!$entity->restricted) { 200 if (!$entity->restricted) {
104 return $this->createEntityPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn); 201 return $this->createEntityPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
105 } else { 202 } else {
106 - $hasAccess = $entity->hasRestriction($role->id, $action); 203 + $hasAccess = $entity->hasActiveRestriction($role->id, $action);
107 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); 204 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
108 } 205 }
109 206
110 } elseif ($entity->isA('chapter')) { 207 } elseif ($entity->isA('chapter')) {
111 208
112 if (!$entity->restricted) { 209 if (!$entity->restricted) {
113 - $hasAccessToBook = $entity->book->hasRestriction($role->id, $action); 210 + $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $action);
211 + $hasPermissiveAccessToBook = !$entity->book->restricted;
114 return $this->createEntityPermissionDataArray($entity, $role, $action, 212 return $this->createEntityPermissionDataArray($entity, $role, $action,
115 - ($roleHasPermission && $hasAccessToBook), ($roleHasPermissionOwn && $hasAccessToBook)); 213 + ($hasExplicitAccessToBook || ($roleHasPermission && $hasPermissiveAccessToBook)),
214 + ($hasExplicitAccessToBook || ($roleHasPermissionOwn && $hasPermissiveAccessToBook)));
116 } else { 215 } else {
117 - $hasAccess = $entity->hasRestriction($role->id, $action); 216 + $hasAccess = $entity->hasActiveRestriction($role->id, $action);
118 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); 217 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
119 } 218 }
120 219
121 } elseif ($entity->isA('page')) { 220 } elseif ($entity->isA('page')) {
122 221
123 if (!$entity->restricted) { 222 if (!$entity->restricted) {
124 - $hasAccessToBook = $entity->book->hasRestriction($role->id, $action); 223 + $hasExplicitAccessToBook = $entity->book->hasActiveRestriction($role->id, $action);
125 - $hasAccessToChapter = $entity->chapter ? ($entity->chapter->hasRestriction($role->id, $action)) : true; 224 + $hasPermissiveAccessToBook = !$entity->book->restricted;
225 + $hasExplicitAccessToChapter = $entity->chapter && $entity->chapter->hasActiveRestriction($role->id, $action);
226 + $hasPermissiveAccessToChapter = $entity->chapter && !$entity->chapter->restricted;
227 + $acknowledgeChapter = ($entity->chapter && $entity->chapter->restricted);
228 +
229 + $hasExplicitAccessToParents = $acknowledgeChapter ? $hasExplicitAccessToChapter : $hasExplicitAccessToBook;
230 + $hasPermissiveAccessToParents = $acknowledgeChapter ? $hasPermissiveAccessToChapter : $hasPermissiveAccessToBook;
231 +
126 return $this->createEntityPermissionDataArray($entity, $role, $action, 232 return $this->createEntityPermissionDataArray($entity, $role, $action,
127 - ($roleHasPermission && $hasAccessToBook && $hasAccessToChapter), 233 + ($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
128 - ($roleHasPermissionOwn && $hasAccessToBook && $hasAccessToChapter)); 234 + ($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
235 + );
129 } else { 236 } else {
130 $hasAccess = $entity->hasRestriction($role->id, $action); 237 $hasAccess = $entity->hasRestriction($role->id, $action);
131 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); 238 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
...@@ -134,6 +241,16 @@ class RestrictionService ...@@ -134,6 +241,16 @@ class RestrictionService
134 } 241 }
135 } 242 }
136 243
244 + /**
245 + * Create an array of data with the information of an entity permissions.
246 + * Used to build data for bulk insertion.
247 + * @param Entity $entity
248 + * @param Role $role
249 + * @param $action
250 + * @param $permissionAll
251 + * @param $permissionOwn
252 + * @return array
253 + */
137 protected function createEntityPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn) 254 protected function createEntityPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
138 { 255 {
139 $entityClass = get_class($entity); 256 $entityClass = get_class($entity);
...@@ -151,22 +268,30 @@ class RestrictionService ...@@ -151,22 +268,30 @@ class RestrictionService
151 /** 268 /**
152 * Checks if an entity has a restriction set upon it. 269 * Checks if an entity has a restriction set upon it.
153 * @param Entity $entity 270 * @param Entity $entity
154 - * @param $action 271 + * @param $permission
155 * @return bool 272 * @return bool
156 */ 273 */
157 - public function checkIfEntityRestricted(Entity $entity, $action) 274 + public function checkEntityUserAccess(Entity $entity, $permission)
158 { 275 {
159 if ($this->isAdmin) return true; 276 if ($this->isAdmin) return true;
160 - $this->currentAction = $action; 277 + $explodedPermission = explode('-', $permission);
278 +
161 $baseQuery = $entity->where('id', '=', $entity->id); 279 $baseQuery = $entity->where('id', '=', $entity->id);
162 - if ($entity->isA('page')) { 280 +
163 - return $this->pageRestrictionQuery($baseQuery)->count() > 0; 281 + $nonEntityPermissions = ['restrictions'];
164 - } elseif ($entity->isA('chapter')) { 282 +
165 - return $this->chapterRestrictionQuery($baseQuery)->count() > 0; 283 + // Handle non entity specific permissions
166 - } elseif ($entity->isA('book')) { 284 + if (in_array($explodedPermission[0], $nonEntityPermissions)) {
167 - return $this->bookRestrictionQuery($baseQuery)->count() > 0; 285 + $allPermission = $this->currentUser && $this->currentUser->can($permission . '-all');
286 + $ownPermission = $this->currentUser && $this->currentUser->can($permission . '-own');
287 + $this->currentAction = 'view';
288 + $isOwner = $this->currentUser && $this->currentUser->id === $entity->created_by;
289 + return ($allPermission || ($isOwner && $ownPermission));
168 } 290 }
169 - return false; 291 +
292 + $action = end($explodedPermission);
293 + $this->currentAction = $action;
294 + return $this->entityRestrictionQuery($baseQuery)->count() > 0;
170 } 295 }
171 296
172 /** 297 /**
...@@ -189,29 +314,6 @@ class RestrictionService ...@@ -189,29 +314,6 @@ class RestrictionService
189 } 314 }
190 315
191 /** 316 /**
192 - * Add restrictions for a page query
193 - * @param $query
194 - * @param string $action
195 - * @return mixed
196 - */
197 - public function enforcePageRestrictions($query, $action = 'view')
198 - {
199 - // Prevent drafts being visible to others.
200 - $query = $query->where(function ($query) {
201 - $query->where('draft', '=', false);
202 - if ($this->currentUser) {
203 - $query->orWhere(function ($query) {
204 - $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
205 - });
206 - }
207 - });
208 -
209 - if ($this->isAdmin) return $query;
210 - $this->currentAction = $action;
211 - return $this->entityRestrictionQuery($query);
212 - }
213 -
214 - /**
215 * The general query filter to remove all entities 317 * The general query filter to remove all entities
216 * that the current user does not have access to. 318 * that the current user does not have access to.
217 * @param $query 319 * @param $query
...@@ -235,6 +337,29 @@ class RestrictionService ...@@ -235,6 +337,29 @@ class RestrictionService
235 } 337 }
236 338
237 /** 339 /**
340 + * Add restrictions for a page query
341 + * @param $query
342 + * @param string $action
343 + * @return mixed
344 + */
345 + public function enforcePageRestrictions($query, $action = 'view')
346 + {
347 + // Prevent drafts being visible to others.
348 + $query = $query->where(function ($query) {
349 + $query->where('draft', '=', false);
350 + if ($this->currentUser) {
351 + $query->orWhere(function ($query) {
352 + $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
353 + });
354 + }
355 + });
356 +
357 + if ($this->isAdmin) return $query;
358 + $this->currentAction = $action;
359 + return $this->entityRestrictionQuery($query);
360 + }
361 +
362 + /**
238 * Add on permission restrictions to a chapter query. 363 * Add on permission restrictions to a chapter query.
239 * @param $query 364 * @param $query
240 * @param string $action 365 * @param string $action
...@@ -316,7 +441,7 @@ class RestrictionService ...@@ -316,7 +441,7 @@ class RestrictionService
316 ->where(function ($query) { 441 ->where(function ($query) {
317 $query->where('has_permission', '=', true)->orWhere(function ($query) { 442 $query->where('has_permission', '=', true)->orWhere(function ($query) {
318 $query->where('has_permission_own', '=', true) 443 $query->where('has_permission_own', '=', true)
319 - ->where('created_by', '=', $this->currentUser->id); 444 + ->where('created_by', '=', $this->currentUser ? $this->currentUser->id : 0);
320 }); 445 });
321 }); 446 });
322 }); 447 });
......
...@@ -45,20 +45,8 @@ function userCan($permission, \BookStack\Ownable $ownable = null) ...@@ -45,20 +45,8 @@ function userCan($permission, \BookStack\Ownable $ownable = null)
45 } 45 }
46 46
47 // Check permission on ownable item 47 // Check permission on ownable item
48 - $permissionBaseName = strtolower($permission) . '-';
49 - $hasPermission = false;
50 - if (auth()->user()->can($permissionBaseName . 'all')) $hasPermission = true;
51 - if (auth()->user()->can($permissionBaseName . 'own') && $ownable->createdBy && $ownable->createdBy->id === auth()->user()->id) $hasPermission = true;
52 -
53 - if (!$ownable instanceof \BookStack\Entity) return $hasPermission;
54 -
55 - // Check restrictions on the entity
56 $restrictionService = app('BookStack\Services\RestrictionService'); 48 $restrictionService = app('BookStack\Services\RestrictionService');
57 - $explodedPermission = explode('-', $permission); 49 + return $restrictionService->checkEntityUserAccess($ownable, $permission);
58 - $action = end($explodedPermission);
59 - $hasAccess = $restrictionService->checkIfEntityRestricted($ownable, $action);
60 - $restrictionsSet = $restrictionService->checkIfRestrictionsSet($ownable, $action);
61 - return ($hasAccess && $restrictionsSet) || (!$restrictionsSet && $hasPermission);
62 } 50 }
63 51
64 /** 52 /**
......
...@@ -23,6 +23,7 @@ class AddViewPermissionsToRoles extends Migration ...@@ -23,6 +23,7 @@ class AddViewPermissionsToRoles extends Migration
23 $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op)); 23 $newPermission->name = strtolower($entity) . '-' . strtolower(str_replace(' ', '-', $op));
24 $newPermission->display_name = $op . ' ' . $entity . 's'; 24 $newPermission->display_name = $op . ' ' . $entity . 's';
25 $newPermission->save(); 25 $newPermission->save();
26 + // Assign view permissions to all current roles
26 foreach ($currentRoles as $role) { 27 foreach ($currentRoles as $role) {
27 $role->attachPermission($newPermission); 28 $role->attachPermission($newPermission);
28 } 29 }
......
...@@ -4,12 +4,14 @@ class RestrictionsTest extends TestCase ...@@ -4,12 +4,14 @@ class RestrictionsTest extends TestCase
4 { 4 {
5 protected $user; 5 protected $user;
6 protected $viewer; 6 protected $viewer;
7 + protected $restrictionService;
7 8
8 public function setUp() 9 public function setUp()
9 { 10 {
10 parent::setUp(); 11 parent::setUp();
11 $this->user = $this->getNewUser(); 12 $this->user = $this->getNewUser();
12 $this->viewer = $this->getViewer(); 13 $this->viewer = $this->getViewer();
14 + $this->restrictionService = $this->app[\BookStack\Services\RestrictionService::class];
13 } 15 }
14 16
15 protected function getViewer() 17 protected function getViewer()
...@@ -43,6 +45,8 @@ class RestrictionsTest extends TestCase ...@@ -43,6 +45,8 @@ class RestrictionsTest extends TestCase
43 } 45 }
44 $entity->save(); 46 $entity->save();
45 $entity->load('restrictions'); 47 $entity->load('restrictions');
48 + $this->restrictionService->buildEntityPermissionsForEntity($entity);
49 + $entity->load('permissions');
46 } 50 }
47 51
48 public function test_book_view_restriction() 52 public function test_book_view_restriction()
......
...@@ -7,7 +7,15 @@ class RolesTest extends TestCase ...@@ -7,7 +7,15 @@ class RolesTest extends TestCase
7 public function setUp() 7 public function setUp()
8 { 8 {
9 parent::setUp(); 9 parent::setUp();
10 - $this->user = $this->getNewBlankUser(); 10 + $this->user = $this->getViewer();
11 + }
12 +
13 + protected function getViewer()
14 + {
15 + $role = \BookStack\Role::getRole('viewer');
16 + $viewer = $this->getNewBlankUser();
17 + $viewer->attachRole($role);;
18 + return $viewer;
11 } 19 }
12 20
13 /** 21 /**
...@@ -141,7 +149,7 @@ class RolesTest extends TestCase ...@@ -141,7 +149,7 @@ class RolesTest extends TestCase
141 149
142 public function test_restrictions_manage_own_permission() 150 public function test_restrictions_manage_own_permission()
143 { 151 {
144 - $otherUsersPage = \BookStack\Page::take(1)->get()->first(); 152 + $otherUsersPage = \BookStack\Page::first();
145 $content = $this->createEntityChainBelongingToUser($this->user); 153 $content = $this->createEntityChainBelongingToUser($this->user);
146 // Check can't restrict other's content 154 // Check can't restrict other's content
147 $this->actingAs($this->user)->visit($otherUsersPage->getUrl()) 155 $this->actingAs($this->user)->visit($otherUsersPage->getUrl())
......
...@@ -65,6 +65,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase ...@@ -65,6 +65,8 @@ class TestCase extends Illuminate\Foundation\Testing\TestCase
65 $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]); 65 $page = factory(BookStack\Page::class)->create(['created_by' => $creatorUser->id, 'updated_by' => $updaterUser->id, 'book_id' => $book->id]);
66 $book->chapters()->saveMany([$chapter]); 66 $book->chapters()->saveMany([$chapter]);
67 $chapter->pages()->saveMany([$page]); 67 $chapter->pages()->saveMany([$page]);
68 + $restrictionService = $this->app[\BookStack\Services\RestrictionService::class];
69 + $restrictionService->buildEntityPermissionsForEntity($book);
68 return [ 70 return [
69 'book' => $book, 71 'book' => $book,
70 'chapter' => $chapter, 72 'chapter' => $chapter,
......