]> BookStack Code Mirror - bookstack/blob - app/Auth/Permissions/PermissionService.php
Adds laravel-microscope package
[bookstack] / app / Auth / Permissions / PermissionService.php
1 <?php namespace BookStack\Auth\Permissions;
2
3 use BookStack\Auth\Permissions;
4 use BookStack\Auth\Role;
5 use BookStack\Entities\Book;
6 use BookStack\Entities\Entity;
7 use BookStack\Entities\EntityProvider;
8 use BookStack\Ownable;
9 use Illuminate\Database\Connection;
10 use Illuminate\Database\Eloquent\Builder;
11 use Illuminate\Database\Query\Builder as QueryBuilder;
12 use Illuminate\Support\Collection;
13
14 class PermissionService
15 {
16
17     protected $currentAction;
18     protected $isAdminUser;
19     protected $userRoles = false;
20     protected $currentUserModel = false;
21
22     /**
23      * @var Connection
24      */
25     protected $db;
26
27     /**
28      * @var JointPermission
29      */
30     protected $jointPermission;
31
32     /**
33      * @var Role
34      */
35     protected $role;
36
37     /**
38      * @var EntityPermission
39      */
40     protected $entityPermission;
41
42     /**
43      * @var EntityProvider
44      */
45     protected $entityProvider;
46
47     protected $entityCache;
48
49     /**
50      * PermissionService constructor.
51      * @param JointPermission $jointPermission
52      * @param EntityPermission $entityPermission
53      * @param Role $role
54      * @param Connection $db
55      * @param EntityProvider $entityProvider
56      */
57     public function __construct(
58         JointPermission $jointPermission,
59         Permissions\EntityPermission $entityPermission,
60         Role $role,
61         Connection $db,
62         EntityProvider $entityProvider
63     ) {
64         $this->db = $db;
65         $this->jointPermission = $jointPermission;
66         $this->entityPermission = $entityPermission;
67         $this->role = $role;
68         $this->entityProvider = $entityProvider;
69     }
70
71     /**
72      * Set the database connection
73      * @param Connection $connection
74      */
75     public function setConnection(Connection $connection)
76     {
77         $this->db = $connection;
78     }
79
80     /**
81      * Prepare the local entity cache and ensure it's empty
82      * @param \BookStack\Entities\Entity[] $entities
83      */
84     protected function readyEntityCache($entities = [])
85     {
86         $this->entityCache = [];
87
88         foreach ($entities as $entity) {
89             $type = $entity->getType();
90             if (!isset($this->entityCache[$type])) {
91                 $this->entityCache[$type] = collect();
92             }
93             $this->entityCache[$type]->put($entity->id, $entity);
94         }
95     }
96
97     /**
98      * Get a book via ID, Checks local cache
99      * @param $bookId
100      * @return Book
101      */
102     protected function getBook($bookId)
103     {
104         if (isset($this->entityCache['book']) && $this->entityCache['book']->has($bookId)) {
105             return $this->entityCache['book']->get($bookId);
106         }
107
108         $book = $this->entityProvider->book->find($bookId);
109         if ($book === null) {
110             $book = false;
111         }
112
113         return $book;
114     }
115
116     /**
117      * Get a chapter via ID, Checks local cache
118      * @param $chapterId
119      * @return \BookStack\Entities\Book
120      */
121     protected function getChapter($chapterId)
122     {
123         if (isset($this->entityCache['chapter']) && $this->entityCache['chapter']->has($chapterId)) {
124             return $this->entityCache['chapter']->get($chapterId);
125         }
126
127         $chapter = $this->entityProvider->chapter->find($chapterId);
128         if ($chapter === null) {
129             $chapter = false;
130         }
131
132         return $chapter;
133     }
134
135     /**
136      * Get the roles for the current user;
137      * @return array|bool
138      */
139     protected function getRoles()
140     {
141         if ($this->userRoles !== false) {
142             return $this->userRoles;
143         }
144
145         $roles = [];
146
147         if (auth()->guest()) {
148             $roles[] = $this->role->getSystemRole('public')->id;
149             return $roles;
150         }
151
152
153         foreach ($this->currentUser()->roles as $role) {
154             $roles[] = $role->id;
155         }
156         return $roles;
157     }
158
159     /**
160      * Re-generate all entity permission from scratch.
161      */
162     public function buildJointPermissions()
163     {
164         $this->jointPermission->truncate();
165         $this->readyEntityCache();
166
167         // Get all roles (Should be the most limited dimension)
168         $roles = $this->role->with('permissions')->get()->all();
169
170         // Chunk through all books
171         $this->bookFetchQuery()->chunk(5, function ($books) use ($roles) {
172             $this->buildJointPermissionsForBooks($books, $roles);
173         });
174
175         // Chunk through all bookshelves
176         $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
177             ->chunk(50, function ($shelves) use ($roles) {
178                 $this->buildJointPermissionsForShelves($shelves, $roles);
179             });
180     }
181
182     /**
183      * Get a query for fetching a book with it's children.
184      * @return QueryBuilder
185      */
186     protected function bookFetchQuery()
187     {
188         return $this->entityProvider->book->newQuery()
189             ->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
190                 $query->select(['id', 'restricted', 'created_by', 'book_id']);
191             }, 'pages'  => function ($query) {
192                 $query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
193             }]);
194     }
195
196     /**
197      * @param Collection $shelves
198      * @param array $roles
199      * @param bool $deleteOld
200      * @throws \Throwable
201      */
202     protected function buildJointPermissionsForShelves($shelves, $roles, $deleteOld = false)
203     {
204         if ($deleteOld) {
205             $this->deleteManyJointPermissionsForEntities($shelves->all());
206         }
207         $this->createManyJointPermissions($shelves, $roles);
208     }
209
210     /**
211      * Build joint permissions for an array of books
212      * @param Collection $books
213      * @param array $roles
214      * @param bool $deleteOld
215      */
216     protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
217     {
218         $entities = clone $books;
219
220         /** @var Book $book */
221         foreach ($books->all() as $book) {
222             foreach ($book->getRelation('chapters') as $chapter) {
223                 $entities->push($chapter);
224             }
225             foreach ($book->getRelation('pages') as $page) {
226                 $entities->push($page);
227             }
228         }
229
230         if ($deleteOld) {
231             $this->deleteManyJointPermissionsForEntities($entities->all());
232         }
233         $this->createManyJointPermissions($entities, $roles);
234     }
235
236     /**
237      * Rebuild the entity jointPermissions for a particular entity.
238      * @param \BookStack\Entities\Entity $entity
239      * @throws \Throwable
240      */
241     public function buildJointPermissionsForEntity(Entity $entity)
242     {
243         $entities = [$entity];
244         if ($entity->isA('book')) {
245             $books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get();
246             $this->buildJointPermissionsForBooks($books, $this->role->newQuery()->get(), true);
247             return;
248         }
249
250         if ($entity->book) {
251             $entities[] = $entity->book;
252         }
253
254         if ($entity->isA('page') && $entity->chapter_id) {
255             $entities[] = $entity->chapter;
256         }
257
258         if ($entity->isA('chapter')) {
259             foreach ($entity->pages as $page) {
260                 $entities[] = $page;
261             }
262         }
263
264         $this->buildJointPermissionsForEntities(collect($entities));
265     }
266
267     /**
268      * Rebuild the entity jointPermissions for a collection of entities.
269      * @param Collection $entities
270      * @throws \Throwable
271      */
272     public function buildJointPermissionsForEntities(Collection $entities)
273     {
274         $roles = $this->role->newQuery()->get();
275         $this->deleteManyJointPermissionsForEntities($entities->all());
276         $this->createManyJointPermissions($entities, $roles);
277     }
278
279     /**
280      * Build the entity jointPermissions for a particular role.
281      * @param Role $role
282      */
283     public function buildJointPermissionForRole(Role $role)
284     {
285         $roles = [$role];
286         $this->deleteManyJointPermissionsForRoles($roles);
287
288         // Chunk through all books
289         $this->bookFetchQuery()->chunk(20, function ($books) use ($roles) {
290             $this->buildJointPermissionsForBooks($books, $roles);
291         });
292
293         // Chunk through all bookshelves
294         $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
295             ->chunk(50, function ($shelves) use ($roles) {
296                 $this->buildJointPermissionsForShelves($shelves, $roles);
297             });
298     }
299
300     /**
301      * Delete the entity jointPermissions attached to a particular role.
302      * @param Role $role
303      */
304     public function deleteJointPermissionsForRole(Role $role)
305     {
306         $this->deleteManyJointPermissionsForRoles([$role]);
307     }
308
309     /**
310      * Delete all of the entity jointPermissions for a list of entities.
311      * @param Role[] $roles
312      */
313     protected function deleteManyJointPermissionsForRoles($roles)
314     {
315         $roleIds = array_map(function ($role) {
316             return $role->id;
317         }, $roles);
318         $this->jointPermission->newQuery()->whereIn('role_id', $roleIds)->delete();
319     }
320
321     /**
322      * Delete the entity jointPermissions for a particular entity.
323      * @param Entity $entity
324      * @throws \Throwable
325      */
326     public function deleteJointPermissionsForEntity(Entity $entity)
327     {
328         $this->deleteManyJointPermissionsForEntities([$entity]);
329     }
330
331     /**
332      * Delete all of the entity jointPermissions for a list of entities.
333      * @param \BookStack\Entities\Entity[] $entities
334      * @throws \Throwable
335      */
336     protected function deleteManyJointPermissionsForEntities($entities)
337     {
338         if (count($entities) === 0) {
339             return;
340         }
341
342         $this->db->transaction(function () use ($entities) {
343
344             foreach (array_chunk($entities, 1000) as $entityChunk) {
345                 $query = $this->db->table('joint_permissions');
346                 foreach ($entityChunk as $entity) {
347                     $query->orWhere(function (QueryBuilder $query) use ($entity) {
348                         $query->where('entity_id', '=', $entity->id)
349                             ->where('entity_type', '=', $entity->getMorphClass());
350                     });
351                 }
352                 $query->delete();
353             }
354         });
355     }
356
357     /**
358      * Create & Save entity jointPermissions for many entities and jointPermissions.
359      * @param Collection $entities
360      * @param array $roles
361      * @throws \Throwable
362      */
363     protected function createManyJointPermissions($entities, $roles)
364     {
365         $this->readyEntityCache($entities);
366         $jointPermissions = [];
367
368         // Fetch Entity Permissions and create a mapping of entity restricted statuses
369         $entityRestrictedMap = [];
370         $permissionFetch = $this->entityPermission->newQuery();
371         foreach ($entities as $entity) {
372             $entityRestrictedMap[$entity->getMorphClass() . ':' . $entity->id] = boolval($entity->getRawAttribute('restricted'));
373             $permissionFetch->orWhere(function ($query) use ($entity) {
374                 $query->where('restrictable_id', '=', $entity->id)->where('restrictable_type', '=', $entity->getMorphClass());
375             });
376         }
377         $permissions = $permissionFetch->get();
378
379         // Create a mapping of explicit entity permissions
380         $permissionMap = [];
381         foreach ($permissions as $permission) {
382             $key = $permission->restrictable_type . ':' . $permission->restrictable_id . ':' . $permission->role_id . ':' . $permission->action;
383             $isRestricted = $entityRestrictedMap[$permission->restrictable_type . ':' . $permission->restrictable_id];
384             $permissionMap[$key] = $isRestricted;
385         }
386
387         // Create a mapping of role permissions
388         $rolePermissionMap = [];
389         foreach ($roles as $role) {
390             foreach ($role->permissions as $permission) {
391                 $rolePermissionMap[$role->getRawAttribute('id') . ':' . $permission->getRawAttribute('name')] = true;
392             }
393         }
394
395         // Create Joint Permission Data
396         foreach ($entities as $entity) {
397             foreach ($roles as $role) {
398                 foreach ($this->getActions($entity) as $action) {
399                     $jointPermissions[] = $this->createJointPermissionData($entity, $role, $action, $permissionMap, $rolePermissionMap);
400                 }
401             }
402         }
403
404         $this->db->transaction(function () use ($jointPermissions) {
405             foreach (array_chunk($jointPermissions, 1000) as $jointPermissionChunk) {
406                 $this->db->table('joint_permissions')->insert($jointPermissionChunk);
407             }
408         });
409     }
410
411
412     /**
413      * Get the actions related to an entity.
414      * @param \BookStack\Entities\Entity $entity
415      * @return array
416      */
417     protected function getActions(Entity $entity)
418     {
419         $baseActions = ['view', 'update', 'delete'];
420         if ($entity->isA('chapter') || $entity->isA('book')) {
421             $baseActions[] = 'page-create';
422         }
423         if ($entity->isA('book')) {
424             $baseActions[] = 'chapter-create';
425         }
426         return $baseActions;
427     }
428
429     /**
430      * Create entity permission data for an entity and role
431      * for a particular action.
432      * @param Entity $entity
433      * @param Role $role
434      * @param string $action
435      * @param array $permissionMap
436      * @param array $rolePermissionMap
437      * @return array
438      */
439     protected function createJointPermissionData(Entity $entity, Role $role, $action, $permissionMap, $rolePermissionMap)
440     {
441         $permissionPrefix = (strpos($action, '-') === false ? ($entity->getType() . '-') : '') . $action;
442         $roleHasPermission = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-all']);
443         $roleHasPermissionOwn = isset($rolePermissionMap[$role->getRawAttribute('id') . ':' . $permissionPrefix . '-own']);
444         $explodedAction = explode('-', $action);
445         $restrictionAction = end($explodedAction);
446
447         if ($role->system_name === 'admin') {
448             return $this->createJointPermissionDataArray($entity, $role, $action, true, true);
449         }
450
451         if ($entity->restricted) {
452             $hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction);
453             return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess);
454         }
455
456         if ($entity->isA('book') || $entity->isA('bookshelf')) {
457             return $this->createJointPermissionDataArray($entity, $role, $action, $roleHasPermission, $roleHasPermissionOwn);
458         }
459
460         // For chapters and pages, Check if explicit permissions are set on the Book.
461         $book = $this->getBook($entity->book_id);
462         $hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $book, $role, $restrictionAction);
463         $hasPermissiveAccessToParents = !$book->restricted;
464
465         // For pages with a chapter, Check if explicit permissions are set on the Chapter
466         if ($entity->isA('page') && $entity->chapter_id !== 0 && $entity->chapter_id !== '0') {
467             $chapter = $this->getChapter($entity->chapter_id);
468             $hasPermissiveAccessToParents = $hasPermissiveAccessToParents && !$chapter->restricted;
469             if ($chapter->restricted) {
470                 $hasExplicitAccessToParents = $this->mapHasActiveRestriction($permissionMap, $chapter, $role, $restrictionAction);
471             }
472         }
473
474         return $this->createJointPermissionDataArray(
475             $entity,
476             $role,
477             $action,
478             ($hasExplicitAccessToParents || ($roleHasPermission && $hasPermissiveAccessToParents)),
479             ($hasExplicitAccessToParents || ($roleHasPermissionOwn && $hasPermissiveAccessToParents))
480         );
481     }
482
483     /**
484      * Check for an active restriction in an entity map.
485      * @param $entityMap
486      * @param Entity $entity
487      * @param Role $role
488      * @param $action
489      * @return bool
490      */
491     protected function mapHasActiveRestriction($entityMap, Entity $entity, Role $role, $action)
492     {
493         $key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action;
494         return isset($entityMap[$key]) ? $entityMap[$key] : false;
495     }
496
497     /**
498      * Create an array of data with the information of an entity jointPermissions.
499      * Used to build data for bulk insertion.
500      * @param \BookStack\Entities\Entity $entity
501      * @param Role $role
502      * @param $action
503      * @param $permissionAll
504      * @param $permissionOwn
505      * @return array
506      */
507     protected function createJointPermissionDataArray(Entity $entity, Role $role, $action, $permissionAll, $permissionOwn)
508     {
509         return [
510             'role_id'            => $role->getRawAttribute('id'),
511             'entity_id'          => $entity->getRawAttribute('id'),
512             'entity_type'        => $entity->getMorphClass(),
513             'action'             => $action,
514             'has_permission'     => $permissionAll,
515             'has_permission_own' => $permissionOwn,
516             'created_by'         => $entity->getRawAttribute('created_by')
517         ];
518     }
519
520     /**
521      * Checks if an entity has a restriction set upon it.
522      * @param Ownable $ownable
523      * @param $permission
524      * @return bool
525      */
526     public function checkOwnableUserAccess(Ownable $ownable, $permission)
527     {
528         $explodedPermission = explode('-', $permission);
529
530         $baseQuery = $ownable->where('id', '=', $ownable->id);
531         $action = end($explodedPermission);
532         $this->currentAction = $action;
533
534         $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment'];
535
536         // Handle non entity specific jointPermissions
537         if (in_array($explodedPermission[0], $nonJointPermissions)) {
538             $allPermission = $this->currentUser() && $this->currentUser()->can($permission . '-all');
539             $ownPermission = $this->currentUser() && $this->currentUser()->can($permission . '-own');
540             $this->currentAction = 'view';
541             $isOwner = $this->currentUser() && $this->currentUser()->id === $ownable->created_by;
542             return ($allPermission || ($isOwner && $ownPermission));
543         }
544
545         // Handle abnormal create jointPermissions
546         if ($action === 'create') {
547             $this->currentAction = $permission;
548         }
549
550         $q = $this->entityRestrictionQuery($baseQuery)->count() > 0;
551         $this->clean();
552         return $q;
553     }
554
555     /**
556      * Checks if a user has the given permission for any items in the system.
557      * Can be passed an entity instance to filter on a specific type.
558      * @param string $permission
559      * @param string $entityClass
560      * @return bool
561      */
562     public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
563     {
564         $userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
565         $userId = $this->currentUser()->id;
566
567         $permissionQuery = $this->db->table('joint_permissions')
568             ->where('action', '=', $permission)
569             ->whereIn('role_id', $userRoleIds)
570             ->where(function ($query) use ($userId) {
571                 $query->where('has_permission', '=', 1)
572                     ->orWhere(function ($query2) use ($userId) {
573                         $query2->where('has_permission_own', '=', 1)
574                             ->where('created_by', '=', $userId);
575                     });
576             });
577
578         if (!is_null($entityClass)) {
579             $entityInstance = app()->make($entityClass);
580             $permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
581         }
582
583         $hasPermission = $permissionQuery->count() > 0;
584         $this->clean();
585         return $hasPermission;
586     }
587
588     /**
589      * Check if an entity has restrictions set on itself or its
590      * parent tree.
591      * @param \BookStack\Entities\Entity $entity
592      * @param $action
593      * @return bool|mixed
594      */
595     public function checkIfRestrictionsSet(Entity $entity, $action)
596     {
597         $this->currentAction = $action;
598         if ($entity->isA('page')) {
599             return $entity->restricted || ($entity->chapter && $entity->chapter->restricted) || $entity->book->restricted;
600         } elseif ($entity->isA('chapter')) {
601             return $entity->restricted || $entity->book->restricted;
602         } elseif ($entity->isA('book')) {
603             return $entity->restricted;
604         }
605     }
606
607     /**
608      * The general query filter to remove all entities
609      * that the current user does not have access to.
610      * @param $query
611      * @return mixed
612      */
613     protected function entityRestrictionQuery($query)
614     {
615         $q = $query->where(function ($parentQuery) {
616             $parentQuery->whereHas('jointPermissions', function ($permissionQuery) {
617                 $permissionQuery->whereIn('role_id', $this->getRoles())
618                     ->where('action', '=', $this->currentAction)
619                     ->where(function ($query) {
620                         $query->where('has_permission', '=', true)
621                             ->orWhere(function ($query) {
622                                 $query->where('has_permission_own', '=', true)
623                                     ->where('created_by', '=', $this->currentUser()->id);
624                             });
625                     });
626             });
627         });
628         $this->clean();
629         return $q;
630     }
631
632     /**
633      * Limited the given entity query so that the query will only
634      * return items that the user has permission for the given ability.
635      */
636     public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
637     {
638         $this->clean();
639         return $query->where(function (Builder $parentQuery) use ($ability) {
640             $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
641                 $permissionQuery->whereIn('role_id', $this->getRoles())
642                     ->where('action', '=', $ability)
643                     ->where(function (Builder $query) {
644                         $query->where('has_permission', '=', true)
645                             ->orWhere(function (Builder $query) {
646                                 $query->where('has_permission_own', '=', true)
647                                     ->where('created_by', '=', $this->currentUser()->id);
648                             });
649                     });
650             });
651         });
652     }
653
654     /**
655      * Extend the given page query to ensure draft items are not visible
656      * unless created by the given user.
657      */
658     public function enforceDraftVisiblityOnQuery(Builder $query): Builder
659     {
660         return $query->where(function (Builder $query) {
661             $query->where('draft', '=', false)
662                 ->orWhere(function (Builder $query) {
663                     $query->where('draft', '=', true)
664                         ->where('created_by', '=', $this->currentUser()->id);
665                 });
666         });
667     }
668
669     /**
670      * Add restrictions for a generic entity
671      * @param string $entityType
672      * @param Builder|\BookStack\Entities\Entity $query
673      * @param string $action
674      * @return Builder
675      */
676     public function enforceEntityRestrictions($entityType, $query, $action = 'view')
677     {
678         if (strtolower($entityType) === 'page') {
679             // Prevent drafts being visible to others.
680             $query = $query->where(function ($query) {
681                 $query->where('draft', '=', false)
682                     ->orWhere(function ($query) {
683                         $query->where('draft', '=', true)
684                             ->where('created_by', '=', $this->currentUser()->id);
685                     });
686             });
687         }
688
689         $this->currentAction = $action;
690         return $this->entityRestrictionQuery($query);
691     }
692
693     /**
694      * Filter items that have entities set as a polymorphic relation.
695      * @param $query
696      * @param string $tableName
697      * @param string $entityIdColumn
698      * @param string $entityTypeColumn
699      * @param string $action
700      * @return QueryBuilder
701      */
702     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
703     {
704
705         $this->currentAction = $action;
706         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn];
707
708         $q = $query->where(function ($query) use ($tableDetails) {
709             $query->whereExists(function ($permissionQuery) use (&$tableDetails) {
710                 $permissionQuery->select('id')->from('joint_permissions')
711                     ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
712                     ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'])
713                     ->where('action', '=', $this->currentAction)
714                     ->whereIn('role_id', $this->getRoles())
715                     ->where(function ($query) {
716                         $query->where('has_permission', '=', true)->orWhere(function ($query) {
717                             $query->where('has_permission_own', '=', true)
718                                 ->where('created_by', '=', $this->currentUser()->id);
719                         });
720                     });
721             });
722         });
723         $this->clean();
724         return $q;
725     }
726
727     /**
728      * Add conditions to a query to filter the selection to related entities
729      * where permissions are granted.
730      * @param $entityType
731      * @param $query
732      * @param $tableName
733      * @param $entityIdColumn
734      * @return mixed
735      */
736     public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn)
737     {
738         $this->currentAction = 'view';
739         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
740
741         $pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass();
742
743         $q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
744             $query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
745                 $query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
746                     $permissionQuery->select('id')->from('joint_permissions')
747                         ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn'])
748                         ->where('entity_type', '=', $pageMorphClass)
749                         ->where('action', '=', $this->currentAction)
750                         ->whereIn('role_id', $this->getRoles())
751                         ->where(function ($query) {
752                             $query->where('has_permission', '=', true)->orWhere(function ($query) {
753                                 $query->where('has_permission_own', '=', true)
754                                     ->where('created_by', '=', $this->currentUser()->id);
755                             });
756                         });
757                 });
758             })->orWhere($tableDetails['entityIdColumn'], '=', 0);
759         });
760
761         $this->clean();
762
763         return $q;
764     }
765
766     /**
767      * Get the current user
768      * @return \BookStack\Auth\User
769      */
770     private function currentUser()
771     {
772         if ($this->currentUserModel === false) {
773             $this->currentUserModel = user();
774         }
775
776         return $this->currentUserModel;
777     }
778
779     /**
780      * Clean the cached user elements.
781      */
782     private function clean()
783     {
784         $this->currentUserModel = false;
785         $this->userRoles = false;
786         $this->isAdminUser = null;
787     }
788 }