]> BookStack Code Mirror - bookstack/blob - app/Services/RestrictionService.php
Added basic system tests for markdown editor, Added extra test helpers
[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      * Add restrictions for a page query
46      * @param $query
47      * @param string $action
48      * @return mixed
49      */
50     public function enforcePageRestrictions($query, $action = 'view')
51     {
52         // Prevent drafts being visible to others.
53         $query = $query->where(function ($query) {
54             $query->where('draft', '=', false);
55             if ($this->currentUser) {
56                 $query->orWhere(function ($query) {
57                     $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser->id);
58                 });
59             }
60         });
61
62         if ($this->isAdmin) return $query;
63         $this->currentAction = $action;
64         return $this->pageRestrictionQuery($query);
65     }
66
67     /**
68      * The base query for restricting pages.
69      * @param $query
70      * @return mixed
71      */
72     private function pageRestrictionQuery($query)
73     {
74         return $query->where(function ($parentWhereQuery) {
75
76             $parentWhereQuery
77                 // (Book & chapter & page) or (Book & page & NO CHAPTER) unrestricted
78                 ->where(function ($query) {
79                     $query->where(function ($query) {
80                         $query->whereExists(function ($query) {
81                             $query->select('*')->from('chapters')
82                                 ->whereRaw('chapters.id=pages.chapter_id')
83                                 ->where('restricted', '=', false);
84                         })->whereExists(function ($query) {
85                             $query->select('*')->from('books')
86                                 ->whereRaw('books.id=pages.book_id')
87                                 ->where('restricted', '=', false);
88                         })->where('restricted', '=', false);
89                     })->orWhere(function ($query) {
90                         $query->where('restricted', '=', false)->where('chapter_id', '=', 0)
91                             ->whereExists(function ($query) {
92                                 $query->select('*')->from('books')
93                                     ->whereRaw('books.id=pages.book_id')
94                                     ->where('restricted', '=', false);
95                             });
96                     });
97                 })
98                 // Page unrestricted, Has no chapter & book has accepted restrictions
99                 ->orWhere(function ($query) {
100                     $query->where('restricted', '=', false)
101                         ->whereExists(function ($query) {
102                             $query->select('*')->from('chapters')
103                                 ->whereRaw('chapters.id=pages.chapter_id');
104                         }, 'and', true)
105                         ->whereExists(function ($query) {
106                             $query->select('*')->from('books')
107                                 ->whereRaw('books.id=pages.book_id')
108                                 ->whereExists(function ($query) {
109                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
110                                 });
111                         });
112                 })
113                 // Page unrestricted, Has an unrestricted chapter & book has accepted restrictions
114                 ->orWhere(function ($query) {
115                     $query->where('restricted', '=', false)
116                         ->whereExists(function ($query) {
117                             $query->select('*')->from('chapters')
118                                 ->whereRaw('chapters.id=pages.chapter_id')->where('restricted', '=', false);
119                         })
120                         ->whereExists(function ($query) {
121                             $query->select('*')->from('books')
122                                 ->whereRaw('books.id=pages.book_id')
123                                 ->whereExists(function ($query) {
124                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
125                                 });
126                         });
127                 })
128                 // Page unrestricted, Has a chapter with accepted permissions
129                 ->orWhere(function ($query) {
130                     $query->where('restricted', '=', false)
131                         ->whereExists(function ($query) {
132                             $query->select('*')->from('chapters')
133                                 ->whereRaw('chapters.id=pages.chapter_id')
134                                 ->where('restricted', '=', true)
135                                 ->whereExists(function ($query) {
136                                     $this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
137                                 });
138                         });
139                 })
140                 // Page has accepted permissions
141                 ->orWhereExists(function ($query) {
142                     $this->checkRestrictionsQuery($query, 'pages', 'Page');
143                 });
144         });
145     }
146
147     /**
148      * Add on permission restrictions to a chapter query.
149      * @param $query
150      * @param string $action
151      * @return mixed
152      */
153     public function enforceChapterRestrictions($query, $action = 'view')
154     {
155         if ($this->isAdmin) return $query;
156         $this->currentAction = $action;
157         return $this->chapterRestrictionQuery($query);
158     }
159
160     /**
161      * The base query for restricting chapters.
162      * @param $query
163      * @return mixed
164      */
165     private function chapterRestrictionQuery($query)
166     {
167         return $query->where(function ($parentWhereQuery) {
168
169             $parentWhereQuery
170                 // Book & chapter unrestricted
171                 ->where(function ($query) {
172                     $query->where('restricted', '=', false)->whereExists(function ($query) {
173                         $query->select('*')->from('books')
174                             ->whereRaw('books.id=chapters.book_id')
175                             ->where('restricted', '=', false);
176                     });
177                 })
178                 // Chapter unrestricted & book has accepted restrictions
179                 ->orWhere(function ($query) {
180                     $query->where('restricted', '=', false)
181                         ->whereExists(function ($query) {
182                             $query->select('*')->from('books')
183                                 ->whereRaw('books.id=chapters.book_id')
184                                 ->whereExists(function ($query) {
185                                     $this->checkRestrictionsQuery($query, 'books', 'Book');
186                                 });
187                         });
188                 })
189                 // Chapter has accepted permissions
190                 ->orWhereExists(function ($query) {
191                     $this->checkRestrictionsQuery($query, 'chapters', 'Chapter');
192                 });
193         });
194     }
195
196     /**
197      * Add restrictions to a book query.
198      * @param $query
199      * @param string $action
200      * @return mixed
201      */
202     public function enforceBookRestrictions($query, $action = 'view')
203     {
204         if ($this->isAdmin) return $query;
205         $this->currentAction = $action;
206         return $this->bookRestrictionQuery($query);
207     }
208
209     /**
210      * The base query for restricting books.
211      * @param $query
212      * @return mixed
213      */
214     private function bookRestrictionQuery($query)
215     {
216         return $query->where(function ($parentWhereQuery) {
217             $parentWhereQuery
218                 ->where('restricted', '=', false)
219                 ->orWhere(function ($query) {
220                     $query->where('restricted', '=', true)->whereExists(function ($query) {
221                         $this->checkRestrictionsQuery($query, 'books', 'Book');
222                     });
223                 });
224         });
225     }
226
227     /**
228      * Filter items that have entities set a a polymorphic relation.
229      * @param $query
230      * @param string $tableName
231      * @param string $entityIdColumn
232      * @param string $entityTypeColumn
233      * @return mixed
234      */
235     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn)
236     {
237         if ($this->isAdmin) return $query;
238         $this->currentAction = 'view';
239         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
240         return $query->where(function ($query) use ($tableDetails) {
241             $query->where(function ($query) use (&$tableDetails) {
242                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Page')
243                     ->whereExists(function ($query) use (&$tableDetails) {
244                         $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
245                             ->where(function ($query) {
246                                 $this->pageRestrictionQuery($query);
247                             });
248                     });
249             })->orWhere(function ($query) use (&$tableDetails) {
250                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Book')->whereExists(function ($query) use (&$tableDetails) {
251                     $query->select('*')->from('books')->whereRaw('books.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
252                         ->where(function ($query) {
253                             $this->bookRestrictionQuery($query);
254                         });
255                 });
256             })->orWhere(function ($query) use (&$tableDetails) {
257                 $query->where($tableDetails['entityTypeColumn'], '=', 'BookStack\Chapter')->whereExists(function ($query) use (&$tableDetails) {
258                     $query->select('*')->from('chapters')->whereRaw('chapters.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
259                         ->where(function ($query) {
260                             $this->chapterRestrictionQuery($query);
261                         });
262                 });
263             });
264         });
265     }
266
267     /**
268      * Filters pages that are a direct relation to another item.
269      * @param $query
270      * @param $tableName
271      * @param $entityIdColumn
272      * @return mixed
273      */
274     public function filterRelatedPages($query, $tableName, $entityIdColumn)
275     {
276         if ($this->isAdmin) return $query;
277         $this->currentAction = 'view';
278         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
279         return $query->where(function ($query) use (&$tableDetails) {
280             $query->where(function ($query) use (&$tableDetails) {
281                 $query->whereExists(function ($query) use (&$tableDetails) {
282                     $query->select('*')->from('pages')->whereRaw('pages.id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
283                         ->where(function ($query) {
284                             $this->pageRestrictionQuery($query);
285                         });
286                 })->orWhere($tableDetails['entityIdColumn'], '=', 0);
287             });
288         });
289     }
290
291     /**
292      * The query to check the restrictions on an entity.
293      * @param $query
294      * @param $tableName
295      * @param $modelName
296      */
297     private function checkRestrictionsQuery($query, $tableName, $modelName)
298     {
299         $query->select('*')->from('restrictions')
300             ->whereRaw('restrictions.restrictable_id=' . $tableName . '.id')
301             ->where('restrictions.restrictable_type', '=', 'BookStack\\' . $modelName)
302             ->where('restrictions.action', '=', $this->currentAction)
303             ->whereIn('restrictions.role_id', $this->userRoles);
304     }
305
306
307 }