]> BookStack Code Mirror - bookstack/blobdiff - app/Auth/Permissions/PermissionService.php
Update settings.php
[bookstack] / app / Auth / Permissions / PermissionService.php
index aae3980c13e78668024b98ba5628f4d550c7c0dd..97cc1ca241e84209f235136550378f3cb8f43f81 100644 (file)
@@ -1,16 +1,14 @@
 <?php namespace BookStack\Auth\Permissions;
 
 use BookStack\Auth\Permissions;
+use BookStack\Auth\Role;
 use BookStack\Entities\Book;
 use BookStack\Entities\Bookshelf;
 use BookStack\Entities\Chapter;
 use BookStack\Entities\Entity;
-use BookStack\Auth\Permissions\EntityPermission;
-use BookStack\Auth\Permissions\JointPermission;
-use BookStack\Ownable;
+use BookStack\Entities\EntityProvider;
 use BookStack\Entities\Page;
-use BookStack\Auth\Role;
-use BookStack\Auth\User;
+use BookStack\Ownable;
 use Illuminate\Database\Connection;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Query\Builder as QueryBuilder;
@@ -24,17 +22,31 @@ class PermissionService
     protected $userRoles = false;
     protected $currentUserModel = false;
 
-    public $book;
-    public $chapter;
-    public $page;
-    public $bookshelf;
-
+    /**
+     * @var Connection
+     */
     protected $db;
 
+    /**
+     * @var JointPermission
+     */
     protected $jointPermission;
+
+    /**
+     * @var Role
+     */
     protected $role;
+
+    /**
+     * @var EntityPermission
+     */
     protected $entityPermission;
 
+    /**
+     * @var EntityProvider
+     */
+    protected $entityProvider;
+
     protected $entityCache;
 
     /**
@@ -43,29 +55,20 @@ class PermissionService
      * @param EntityPermission $entityPermission
      * @param Role $role
      * @param Connection $db
-     * @param Bookshelf $bookshelf
-     * @param Book $book
-     * @param \BookStack\Entities\Chapter $chapter
-     * @param \BookStack\Entities\Page $page
+     * @param EntityProvider $entityProvider
      */
     public function __construct(
         JointPermission $jointPermission,
         Permissions\EntityPermission $entityPermission,
         Role $role,
         Connection $db,
-        Bookshelf $bookshelf,
-        Book $book,
-        Chapter $chapter,
-        Page $page
+        EntityProvider $entityProvider
     ) {
         $this->db = $db;
         $this->jointPermission = $jointPermission;
         $this->entityPermission = $entityPermission;
         $this->role = $role;
-        $this->bookshelf = $bookshelf;
-        $this->book = $book;
-        $this->chapter = $chapter;
-        $this->page = $page;
+        $this->entityProvider = $entityProvider;
     }
 
     /**
@@ -105,7 +108,7 @@ class PermissionService
             return $this->entityCache['book']->get($bookId);
         }
 
-        $book = $this->book->find($bookId);
+        $book = $this->entityProvider->book->find($bookId);
         if ($book === null) {
             $book = false;
         }
@@ -124,7 +127,7 @@ class PermissionService
             return $this->entityCache['chapter']->get($chapterId);
         }
 
-        $chapter = $this->chapter->find($chapterId);
+        $chapter = $this->entityProvider->chapter->find($chapterId);
         if ($chapter === null) {
             $chapter = false;
         }
@@ -173,7 +176,7 @@ class PermissionService
         });
 
         // Chunk through all bookshelves
-        $this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
+        $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
             ->chunk(50, function ($shelves) use ($roles) {
                 $this->buildJointPermissionsForShelves($shelves, $roles);
             });
@@ -185,11 +188,12 @@ class PermissionService
      */
     protected function bookFetchQuery()
     {
-        return $this->book->newQuery()->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
-            $query->select(['id', 'restricted', 'created_by', 'book_id']);
-        }, 'pages'  => function ($query) {
-            $query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
-        }]);
+        return $this->entityProvider->book->newQuery()
+            ->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) {
+                $query->select(['id', 'restricted', 'created_by', 'book_id']);
+            }, 'pages'  => function ($query) {
+                $query->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']);
+            }]);
     }
 
     /**
@@ -211,7 +215,6 @@ class PermissionService
      * @param Collection $books
      * @param array $roles
      * @param bool $deleteOld
-     * @throws \Throwable
      */
     protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false)
     {
@@ -291,7 +294,7 @@ class PermissionService
         });
 
         // Chunk through all bookshelves
-        $this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
+        $this->entityProvider->bookshelf->newQuery()->select(['id', 'restricted', 'created_by'])
             ->chunk(50, function ($shelves) use ($roles) {
                 $this->buildJointPermissionsForShelves($shelves, $roles);
             });
@@ -552,6 +555,39 @@ class PermissionService
         return $q;
     }
 
+    /**
+     * Checks if a user has the given permission for any items in the system.
+     * Can be passed an entity instance to filter on a specific type.
+     * @param string $permission
+     * @param string $entityClass
+     * @return bool
+     */
+    public function checkUserHasPermissionOnAnything(string $permission, string $entityClass = null)
+    {
+        $userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray();
+        $userId = $this->currentUser()->id;
+
+        $permissionQuery = $this->db->table('joint_permissions')
+            ->where('action', '=', $permission)
+            ->whereIn('role_id', $userRoleIds)
+            ->where(function ($query) use ($userId) {
+                $query->where('has_permission', '=', 1)
+                    ->orWhere(function ($query2) use ($userId) {
+                        $query2->where('has_permission_own', '=', 1)
+                            ->where('created_by', '=', $userId);
+                    });
+            });
+
+        if (!is_null($entityClass)) {
+            $entityInstance = app()->make($entityClass);
+            $permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass());
+        }
+
+        $hasPermission = $permissionQuery->count() > 0;
+        $this->clean();
+        return $hasPermission;
+    }
+
     /**
      * Check if an entity has restrictions set on itself or its
      * parent tree.
@@ -597,40 +633,40 @@ class PermissionService
     }
 
     /**
-     * Get the children of a book in an efficient single query, Filtered by the permission system.
-     * @param integer $book_id
-     * @param bool $filterDrafts
-     * @param bool $fetchPageContent
-     * @return QueryBuilder
+     * Limited the given entity query so that the query will only
+     * return items that the user has permission for the given ability.
      */
-    public function bookChildrenQuery($book_id, $filterDrafts = false, $fetchPageContent = false)
+    public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder
     {
-        $pageSelect = $this->db->table('pages')->selectRaw($this->page->entityRawQuery($fetchPageContent))->where('book_id', '=', $book_id)->where(function ($query) use ($filterDrafts) {
-            $query->where('draft', '=', 0);
-            if (!$filterDrafts) {
-                $query->orWhere(function ($query) {
-                    $query->where('draft', '=', 1)->where('created_by', '=', $this->currentUser()->id);
-                });
-            }
-        });
-        $chapterSelect = $this->db->table('chapters')->selectRaw($this->chapter->entityRawQuery())->where('book_id', '=', $book_id);
-        $query = $this->db->query()->select('*')->from($this->db->raw("({$pageSelect->toSql()} UNION {$chapterSelect->toSql()}) AS U"))
-            ->mergeBindings($pageSelect)->mergeBindings($chapterSelect);
-
-        // Add joint permission filter
-        $whereQuery = $this->db->table('joint_permissions as jp')->selectRaw('COUNT(*)')
-            ->whereRaw('jp.entity_id=U.id')->whereRaw('jp.entity_type=U.entity_type')
-            ->where('jp.action', '=', 'view')->whereIn('jp.role_id', $this->getRoles())
-            ->where(function ($query) {
-                $query->where('jp.has_permission', '=', 1)->orWhere(function ($query) {
-                    $query->where('jp.has_permission_own', '=', 1)->where('jp.created_by', '=', $this->currentUser()->id);
-                });
+        $this->clean();
+        return $query->where(function (Builder $parentQuery) use ($ability) {
+            $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) {
+                $permissionQuery->whereIn('role_id', $this->getRoles())
+                    ->where('action', '=', $ability)
+                    ->where(function (Builder $query) {
+                        $query->where('has_permission', '=', true)
+                            ->orWhere(function (Builder $query) {
+                                $query->where('has_permission_own', '=', true)
+                                    ->where('created_by', '=', $this->currentUser()->id);
+                            });
+                    });
             });
-        $query->whereRaw("({$whereQuery->toSql()}) > 0")->mergeBindings($whereQuery);
+        });
+    }
 
-        $query->orderBy('draft', 'desc')->orderBy('priority', 'asc');
-        $this->clean();
-        return  $query;
+    /**
+     * Extend the given page query to ensure draft items are not visible
+     * unless created by the given user.
+     */
+    public function enforceDraftVisiblityOnQuery(Builder $query): Builder
+    {
+        return $query->where(function (Builder $query) {
+            $query->where('draft', '=', false)
+                ->orWhere(function (Builder $query) {
+                    $query->where('draft', '=', true)
+                        ->where('created_by', '=', $this->currentUser()->id);
+                });
+        });
     }
 
     /**
@@ -645,12 +681,11 @@ class PermissionService
         if (strtolower($entityType) === 'page') {
             // Prevent drafts being visible to others.
             $query = $query->where(function ($query) {
-                $query->where('draft', '=', false);
-                if ($this->currentUser()) {
-                    $query->orWhere(function ($query) {
-                        $query->where('draft', '=', true)->where('created_by', '=', $this->currentUser()->id);
+                $query->where('draft', '=', false)
+                    ->orWhere(function ($query) {
+                        $query->where('draft', '=', true)
+                            ->where('created_by', '=', $this->currentUser()->id);
                     });
-                }
             });
         }
 
@@ -665,7 +700,7 @@ class PermissionService
      * @param string $entityIdColumn
      * @param string $entityTypeColumn
      * @param string $action
-     * @return mixed
+     * @return QueryBuilder
      */
     public function filterRestrictedEntityRelations($query, $tableName, $entityIdColumn, $entityTypeColumn, $action = 'view')
     {
@@ -693,18 +728,21 @@ class PermissionService
     }
 
     /**
-     * Filters pages that are a direct relation to another item.
+     * Add conditions to a query to filter the selection to related entities
+     * where permissions are granted.
+     * @param $entityType
      * @param $query
      * @param $tableName
      * @param $entityIdColumn
      * @return mixed
      */
-    public function filterRelatedPages($query, $tableName, $entityIdColumn)
+    public function filterRelatedEntity($entityType, $query, $tableName, $entityIdColumn)
     {
         $this->currentAction = 'view';
         $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn];
 
-        $pageMorphClass = $this->page->getMorphClass();
+        $pageMorphClass = $this->entityProvider->get($entityType)->getMorphClass();
+
         $q = $query->where(function ($query) use ($tableDetails, $pageMorphClass) {
             $query->where(function ($query) use (&$tableDetails, $pageMorphClass) {
                 $query->whereExists(function ($permissionQuery) use (&$tableDetails, $pageMorphClass) {
@@ -722,7 +760,9 @@ class PermissionService
                 });
             })->orWhere($tableDetails['entityIdColumn'], '=', 0);
         });
+
         $this->clean();
+
         return $q;
     }