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