]> BookStack Code Mirror - bookstack/blob - app/Services/RestrictionService.php
847db29fe7d5fd254f61c1f5c91a593fb1cd1172
[bookstack] / app / Services / RestrictionService.php
1 <?php namespace BookStack\Services;
2
3 use BookStack\Book;
4 use BookStack\Chapter;
5 use BookStack\Entity;
6 use BookStack\EntityPermission;
7 use BookStack\Page;
8 use BookStack\Role;
9 use Illuminate\Database\Eloquent\Collection;
10
11 class RestrictionService
12 {
13
14     protected $userRoles;
15     protected $isAdmin;
16     protected $currentAction;
17     protected $currentUser;
18
19     public $book;
20     public $chapter;
21     public $page;
22
23     protected $entityPermission;
24     protected $role;
25
26     protected $actions = ['view', 'create', 'update', 'delete'];
27
28     /**
29      * RestrictionService constructor.
30      * TODO - Handle events when roles or entities change.
31      * @param EntityPermission $entityPermission
32      * @param Book $book
33      * @param Chapter $chapter
34      * @param Page $page
35      * @param Role $role
36      */
37     public function __construct(EntityPermission $entityPermission, Book $book, Chapter $chapter, Page $page, Role $role)
38     {
39         $this->currentUser = auth()->user();
40         $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : [];
41         $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false;
42
43         $this->entityPermission = $entityPermission;
44         $this->role = $role;
45         $this->book = $book;
46         $this->chapter = $chapter;
47         $this->page = $page;
48     }
49
50     /**
51      * Re-generate all entity permission from scratch.
52      */
53     public function buildEntityPermissions()
54     {
55         $this->entityPermission->truncate();
56
57         // Get all roles (Should be the most limited dimension)
58         $roles = $this->role->load('permissions')->all();
59
60         // Chunk through all books
61         $this->book->chunk(500, function ($books) use ($roles) {
62             $this->createManyEntityPermissions($books, $roles);
63         });
64
65         // Chunk through all chapters
66         $this->chapter->with('book')->chunk(500, function ($books) use ($roles) {
67             $this->createManyEntityPermissions($books, $roles);
68         });
69
70         // Chunk through all pages
71         $this->page->with('book', 'chapter')->chunk(500, function ($books) use ($roles) {
72             $this->createManyEntityPermissions($books, $roles);
73         });
74     }
75
76     /**
77      * Create & Save entity permissions for many entities and permissions.
78      * @param Collection $entities
79      * @param Collection $roles
80      */
81     protected function createManyEntityPermissions($entities, $roles)
82     {
83         $entityPermissions = [];
84         foreach ($entities as $entity) {
85             foreach ($roles as $role) {
86                 foreach ($this->actions as $action) {
87                     $entityPermissions[] = $this->createEntityPermissionData($entity, $role, $action);
88                 }
89             }
90         }
91         $this->entityPermission->insert($entityPermissions);
92     }
93
94
95     protected function createEntityPermissionData(Entity $entity, Role $role, $action)
96     {
97         $permissionPrefix = $entity->getType() . '-' . $action;
98         $roleHasPermission = $role->hasPermission($permissionPrefix . '-all');
99         $roleHasPermissionOwn = $role->hasPermission($permissionPrefix . '-own');
100
101         if ($entity->isA('book')) {
102
103             if (!$entity->restricted) {
104                 return $this->createEntityPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
105             } else {
106                 $hasAccess = $entity->hasRestriction($role->id, $action);
107                 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
108             }
109
110         } elseif ($entity->isA('chapter')) {
111
112             if (!$entity->restricted) {
113                 $hasAccessToBook = $entity->book->hasRestriction($role->id, $action);
114                 return $this->createEntityPermissionDataArray($entity, $role, $action,
115                     ($roleHasPermission && $hasAccessToBook), ($roleHasPermissionOwn && $hasAccessToBook));
116             } else {
117                 $hasAccess = $entity->hasRestriction($role->id, $action);
118                 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
119             }
120
121         } elseif ($entity->isA('page')) {
122
123             if (!$entity->restricted) {
124                 $hasAccessToBook = $entity->book->hasRestriction($role->id, $action);
125                 $hasAccessToChapter = $entity->chapter ? ($entity->chapter->hasRestriction($role->id, $action)) : true;
126                 return $this->createEntityPermissionDataArray($entity, $role, $action,
127                     ($roleHasPermission && $hasAccessToBook && $hasAccessToChapter),
128                     ($roleHasPermissionOwn && $hasAccessToBook && $hasAccessToChapter));
129             } else {
130                 $hasAccess = $entity->hasRestriction($role->id, $action);
131                 return $this->createEntityPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
132             }
133
134         }
135     }
136
137     protected function createEntityPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
138     {
139         $entityClass = get_class($entity);
140         return [
141             'role_id'            => $role->id,
142             'entity_id'          => $entity->id,
143             'entity_type'        => $entityClass,
144             'action'             => $action,
145             'has_permission'     => $permissionAll,
146             'has_permission_own' => $permissionOwn,
147             'created_by'         => $entity->created_by
148         ];
149     }
150
151     /**
152      * Checks if an entity has a restriction set upon it.
153      * @param Entity $entity
154      * @param $action
155      * @return bool
156      */
157     public function checkIfEntityRestricted(Entity $entity, $action)
158     {
159         if ($this->isAdmin) return true;
160         $this->currentAction = $action;
161         $baseQuery = $entity->where('id', '=', $entity->id);
162         if ($entity->isA('page')) {
163             return $this->pageRestrictionQuery($baseQuery)->count() > 0;
164         } elseif ($entity->isA('chapter')) {
165             return $this->chapterRestrictionQuery($baseQuery)->count() > 0;
166         } elseif ($entity->isA('book')) {
167             return $this->bookRestrictionQuery($baseQuery)->count() > 0;
168         }
169         return false;
170     }
171
172     /**
173      * Check if an entity has restrictions set on itself or its
174      * parent tree.
175      * @param Entity $entity
176      * @param $action
177      * @return bool|mixed
178      */
179     public function checkIfRestrictionsSet(Entity $entity, $action)
180     {
181         $this->currentAction = $action;
182         if ($entity->isA('page')) {
183             return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
184         } elseif ($entity->isA('chapter')) {
185             return $entity->restricted || $entity->book->restricted;
186         } elseif ($entity->isA('book')) {
187             return $entity->restricted;
188         }
189     }
190
191     /**
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
216      * that the current user does not have access to.
217      * @param $query
218      * @return mixed
219      */
220     protected function entityRestrictionQuery($query)
221     {
222         return $query->where(function ($parentQuery) {
223             $parentQuery->whereHas('permissions', function ($permissionQuery) {
224                 $permissionQuery->whereIn('role_id', $this->userRoles)
225                     ->where('action', '=', $this->currentAction)
226                     ->where(function ($query) {
227                         $query->where('has_permission', '=', true)
228                             ->orWhere(function ($query) {
229                                 $query->where('has_permission_own', '=', true)
230                                     ->where('created_by', '=', $this->currentUser->id);
231                             });
232                     });
233             });
234         });
235     }
236
237     /**
238      * Add on permission restrictions to a chapter query.
239      * @param $query
240      * @param string $action
241      * @return mixed
242      */
243     public function enforceChapterRestrictions($query, $action = 'view')
244     {
245         if ($this->isAdmin) return $query;
246         $this->currentAction = $action;
247         return $this->entityRestrictionQuery($query);
248     }
249
250     /**
251      * Add restrictions to a book query.
252      * @param $query
253      * @param string $action
254      * @return mixed
255      */
256     public function enforceBookRestrictions($query, $action = 'view')
257     {
258         if ($this->isAdmin) return $query;
259         $this->currentAction = $action;
260         return $this->entityRestrictionQuery($query);
261     }
262
263     /**
264      * Filter items that have entities set a a polymorphic relation.
265      * @param $query
266      * @param string $tableName
267      * @param string $entityIdColumn
268      * @param string $entityTypeColumn
269      * @return mixed
270      */
271     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
272     {
273         if ($this->isAdmin) return $query;
274         $this->currentAction = 'view';
275         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
276
277         return $query->where(function ($query) use ($tableDetails) {
278             $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
279                 $permissionQuery->select('id')->from('entity_permissions')
280                     ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
281                     ->whereRaw('entity_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
282                     ->where('action', '=', $this->currentAction)
283                     ->whereIn('role_id', $this->userRoles)
284                     ->where(function ($query) {
285                         $query->where('has_permission', '=', true)->orWhere(function ($query) {
286                             $query->where('has_permission_own', '=', true)
287                                 ->where('created_by', '=', $this->currentUser->id);
288                         });
289                     });
290             });
291         });
292
293     }
294
295     /**
296      * Filters pages that are a direct relation to another item.
297      * @param $query
298      * @param $tableName
299      * @param $entityIdColumn
300      * @return mixed
301      */
302     public function filterRelatedPages($query, $tableName, $entityIdColumn)
303     {
304         if ($this->isAdmin) return $query;
305         $this->currentAction = 'view';
306         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
307
308         return $query->where(function ($query) use ($tableDetails) {
309             $query->where(function ($query) use (&$tableDetails) {
310                 $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
311                     $permissionQuery->select('id')->from('entity_permissions')
312                         ->whereRaw('entity_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
313                         ->where('entity_type', '=', 'Bookstack\\Page')
314                         ->where('action', '=', $this->currentAction)
315                         ->whereIn('role_id', $this->userRoles)
316                         ->where(function ($query) {
317                             $query->where('has_permission', '=', true)->orWhere(function ($query) {
318                                 $query->where('has_permission_own', '=', true)
319                                     ->where('created_by', '=', $this->currentUser->id);
320                             });
321                         });
322                 });
323             })->orWhere($tableDetails['entityIdColumn'], '=', 0);
324         });
325     }
326
327 }