]> BookStack Code Mirror - bookstack/blob - app/Services/RestrictionService.php
Updated entity restrictions to allow permissions, Not just restrict
[bookstack] / app / Services / RestrictionService.php
1 <?php namespace BookStack\Services;
2
3 use BookStack\Entity;
4
5 class RestrictionService
6 {
7
8     protected $userRoles;
9     protected $isAdmin;
10     protected $currentAction;
11     protected $currentUser;
12
13     /**
14      * RestrictionService constructor.
15      */
16     public function __construct()
17     {
18         $this->currentUser = auth()->user();
19         $this->userRoles = $this->currentUser ? $this->currentUser->roles->pluck('id') : [];
20         $this->isAdmin = $this->currentUser ? $this->currentUser->hasRole('admin') : false;
21     }
22
23     /**
24      * Checks if an entity has a restriction set upon it.
25      * @param Entity $entity
26      * @param $action
27      * @return bool
28      */
29     public function checkIfEntityRestricted(Entity $entity, $action)
30     {
31         if ($this->isAdmin) return true;
32         $this->currentAction = $action;
33         $baseQuery = $entity->where('id', '=', $entity->id);
34         if ($entity->isA('page')) {
35             return $this->pageRestrictionQuery($baseQuery)->count() > 0;
36         } elseif ($entity->isA('chapter')) {
37             return $this->chapterRestrictionQuery($baseQuery)->count() > 0;
38         } elseif ($entity->isA('book')) {
39             return $this->bookRestrictionQuery($baseQuery)->count() > 0;
40         }
41         return false;
42     }
43
44     /**
45      * Check if an entity has restrictions set on itself or its
46      * parent tree.
47      * @param Entity $entity
48      * @param $action
49      * @return bool|mixed
50      */
51     public function checkIfRestrictionsSet(Entity $entity, $action)
52     {
53         $this->currentAction = $action;
54         if ($entity->isA('page')) {
55             return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
56         } elseif ($entity->isA('chapter')) {
57             return $entity->restricted || $entity->book->restricted;
58         } elseif ($entity->isA('book')) {
59             return $entity->restricted;
60         }
61     }
62
63     /**
64      * Add restrictions for a page query
65      * @param $query
66      * @param string $action
67      * @return mixed
68      */
69     public function enforcePageRestrictions($query, $action = 'view')
70     {
71         // Prevent drafts being visible to others.
72         $query = $query->where(function ($query) {
73             $query->where('draft', '=', false);
74             if ($this->currentUser) {
75                 $query->orWhere(function ($query) {
76                     $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
77                 });
78             }
79         });
80
81         if ($this->isAdmin) return $query;
82         $this->currentAction = $action;
83         return $this->pageRestrictionQuery($query);
84     }
85
86     /**
87      * The base query for restricting pages.
88      * @param $query
89      * @return mixed
90      */
91     private function pageRestrictionQuery($query)
92     {
93         return $query->where(function ($parentWhereQuery) {
94
95             $parentWhereQuery
96                 // (Book & chapter & page) or (Book & page & NO CHAPTER) unrestricted
97                 ->where(function ($query) {
98                     $query->where(function ($query) {
99                         $query->whereExists(function ($query) {
100                             $query->select('*')->from('chapters')
101                                 ->whereRaw('chapters.id=pages.chapter_id')
102                                 ->where('restricted', '=', false);
103                         })->whereExists(function ($query) {
104                             $query->select('*')->from('books')
105                                 ->whereRaw('books.id=pages.book_id')
106                                 ->where('restricted', '=', false);
107                         })->where('restricted', '=', false);
108                     })->orWhere(function ($query) {
109                         $query->where('restricted', '=', false)->where('chapter_id', '=', 0)
110                             ->whereExists(function ($query) {
111                                 $query->select('*')->from('books')
112                                     ->whereRaw('books.id=pages.book_id')
113                                     ->where('restricted', '=', false);
114                             });
115                     });
116                 })
117                 // Page unrestricted, Has no chapter & book has accepted restrictions
118                 ->orWhere(function ($query) {
119                     $query->where('restricted', '=', false)
120                         ->whereExists(function ($query) {
121                             $query->select('*')->from('chapters')
122                                 ->whereRaw('chapters.id=pages.chapter_id');
123                         }, 'and', true)
124                         ->whereExists(function ($query) {
125                             $query->select('*')->from('books')
126                                 ->whereRaw('books.id=pages.book_id')
127                                 ->whereExists(function ($query) {
128                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
129                                 });
130                         });
131                 })
132                 // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions
133                 ->orWhere(function ($query) {
134                     $query->where('restricted', '=', false)
135                         ->whereExists(function ($query) {
136                             $query->select('*')->from('chapters')
137                                 ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false);
138                         })
139                         ->whereExists(function ($query) {
140                             $query->select('*')->from('books')
141                                 ->whereRaw('books.id=pages.book_id')
142                                 ->whereExists(function ($query) {
143                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
144                                 });
145                         });
146                 })
147                 // Page unrestricted, Has a chapter with accepted permissions
148                 ->orWhere(function ($query) {
149                     $query->where('restricted', '=', false)
150                         ->whereExists(function ($query) {
151                             $query->select('*')->from('chapters')
152                                 ->whereRaw('chapters.id=pages.chapter_id')
153                                 ->where('restricted', '=', true)
154                                 ->whereExists(function ($query) {
155                                     $this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
156                                 });
157                         });
158                 })
159                 // Page has accepted permissions
160                 ->orWhereExists(function ($query) {
161                     $this->checkRestrictionsQuery($query, 'pages', 'Page');
162                 });
163         });
164     }
165
166     /**
167      * Add on permission restrictions to a chapter query.
168      * @param $query
169      * @param string $action
170      * @return mixed
171      */
172     public function enforceChapterRestrictions($query, $action = 'view')
173     {
174         if ($this->isAdmin) return $query;
175         $this->currentAction = $action;
176         return $this->chapterRestrictionQuery($query);
177     }
178
179     /**
180      * The base query for restricting chapters.
181      * @param $query
182      * @return mixed
183      */
184     private function chapterRestrictionQuery($query)
185     {
186         return $query->where(function ($parentWhereQuery) {
187
188             $parentWhereQuery
189                 // Book & chapter unrestricted
190                 ->where(function ($query) {
191                     $query->where('restricted', '=', false)->whereExists(function ($query) {
192                         $query->select('*')->from('books')
193                             ->whereRaw('books.id=chapters.book_id')
194                             ->where('restricted', '=', false);
195                     });
196                 })
197                 // Chapter unrestricted & book has accepted restrictions
198                 ->orWhere(function ($query) {
199                     $query->where('restricted', '=', false)
200                         ->whereExists(function ($query) {
201                             $query->select('*')->from('books')
202                                 ->whereRaw('books.id=chapters.book_id')
203                                 ->whereExists(function ($query) {
204                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
205                                 });
206                         });
207                 })
208                 // Chapter has accepted permissions
209                 ->orWhereExists(function ($query) {
210                     $this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
211                 });
212         });
213     }
214
215     /**
216      * Add restrictions to a book query.
217      * @param $query
218      * @param string $action
219      * @return mixed
220      */
221     public function enforceBookRestrictions($query, $action = 'view')
222     {
223         if ($this->isAdmin) return $query;
224         $this->currentAction = $action;
225         return $this->bookRestrictionQuery($query);
226     }
227
228     /**
229      * The base query for restricting books.
230      * @param $query
231      * @return mixed
232      */
233     private function bookRestrictionQuery($query)
234     {
235         return $query->where(function ($parentWhereQuery) {
236             $parentWhereQuery
237                 ->where('restricted', '=', false)
238                 ->orWhere(function ($query) {
239                     $query->where('restricted', '=', true)->whereExists(function ($query) {
240                         $this->checkRestrictionsQuery($query, 'books', 'Book');
241                     });
242                 });
243         });
244     }
245
246     /**
247      * Filter items that have entities set a a polymorphic relation.
248      * @param $query
249      * @param string $tableName
250      * @param string $entityIdColumn
251      * @param string $entityTypeColumn
252      * @return mixed
253      */
254     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
255     {
256         if ($this->isAdmin) return $query;
257         $this->currentAction = 'view';
258         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
259         return $query->where(function ($query) use ($tableDetails) {
260             $query->where(function ($query) use (&$tableDetails) {
261                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Page')
262                     ->whereExists(function ($query) use (&$tableDetails) {
263                         $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
264                             ->where(function ($query) {
265                                 $this->pageRestrictionQuery($query);
266                             });
267                     });
268             })->orWhere(function ($query) use (&$tableDetails) {
269                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Book')->whereExists(function ($query) use (&$tableDetails) {
270                     $query->select('*')->from('books')->whereRaw('books.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
271                         ->where(function ($query) {
272                             $this->bookRestrictionQuery($query);
273                         });
274                 });
275             })->orWhere(function ($query) use (&$tableDetails) {
276                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Chapter')->whereExists(function ($query) use (&$tableDetails) {
277                     $query->select('*')->from('chapters')->whereRaw('chapters.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
278                         ->where(function ($query) {
279                             $this->chapterRestrictionQuery($query);
280                         });
281                 });
282             });
283         });
284     }
285
286     /**
287      * Filters pages that are a direct relation to another item.
288      * @param $query
289      * @param $tableName
290      * @param $entityIdColumn
291      * @return mixed
292      */
293     public function filterRelatedPages($query, $tableName, $entityIdColumn)
294     {
295         if ($this->isAdmin) return $query;
296         $this->currentAction = 'view';
297         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
298         return $query->where(function ($query) use (&$tableDetails) {
299             $query->where(function ($query) use (&$tableDetails) {
300                 $query->whereExists(function ($query) use (&$tableDetails) {
301                     $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
302                         ->where(function ($query) {
303                             $this->pageRestrictionQuery($query);
304                         });
305                 })->orWhere($tableDetails['entityIdColumn'], '=', 0);
306             });
307         });
308     }
309
310     /**
311      * The query to check the restrictions on an entity.
312      * @param $query
313      * @param $tableName
314      * @param $modelName
315      */
316     private function checkRestrictionsQuery($query, $tableName, $modelName)
317     {
318         $query->select('*')->from('restrictions')
319             ->whereRaw('restrictions.restrictable_id=' . $tableName . '.id')
320             ->where('restrictions.restrictable_type', '=', 'BookStack\\' . $modelName)
321             ->where('restrictions.action', '=', $this->currentAction)
322             ->whereIn('restrictions.role_id', $this->userRoles);
323     }
324
325
326 }