X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/257a5a23ecaf7ce779969d575ff8a0b976181d13..refs/pull/2393/head:/app/Auth/Permissions/PermissionService.php diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index a4b9103ba..5f4648d58 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -2,11 +2,9 @@ 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\Entities\Page; +use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Entity; +use BookStack\Entities\EntityProvider; use BookStack\Ownable; use Illuminate\Database\Connection; use Illuminate\Database\Eloquent\Builder; @@ -21,48 +19,48 @@ 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; /** * PermissionService constructor. - * @param JointPermission $jointPermission - * @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 */ 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; } /** @@ -76,7 +74,7 @@ class PermissionService /** * Prepare the local entity cache and ensure it's empty - * @param \BookStack\Entities\Entity[] $entities + * @param \BookStack\Entities\Models\Entity[] $entities */ protected function readyEntityCache($entities = []) { @@ -102,7 +100,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; } @@ -113,7 +111,7 @@ class PermissionService /** * Get a chapter via ID, Checks local cache * @param $chapterId - * @return \BookStack\Entities\Book + * @return \BookStack\Entities\Models\Book */ protected function getChapter($chapterId) { @@ -121,7 +119,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; } @@ -170,7 +168,7 @@ class PermissionService }); // Chunk through all bookshelves - $this->bookshelf->newQuery()->select(['id', 'restricted', 'created_by']) + $this->entityProvider->bookshelf->newQuery()->withTrashed()->select(['id', 'restricted', 'created_by']) ->chunk(50, function ($shelves) use ($roles) { $this->buildJointPermissionsForShelves($shelves, $roles); }); @@ -182,11 +180,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->withTrashed()->newQuery() + ->select(['id', 'restricted', 'created_by'])->with(['chapters' => function ($query) { + $query->withTrashed()->select(['id', 'restricted', 'created_by', 'book_id']); + }, 'pages' => function ($query) { + $query->withTrashed()->select(['id', 'restricted', 'created_by', 'book_id', 'chapter_id']); + }]); } /** @@ -208,7 +207,6 @@ class PermissionService * @param Collection $books * @param array $roles * @param bool $deleteOld - * @throws \Throwable */ protected function buildJointPermissionsForBooks($books, $roles, $deleteOld = false) { @@ -232,7 +230,7 @@ class PermissionService /** * Rebuild the entity jointPermissions for a particular entity. - * @param \BookStack\Entities\Entity $entity + * @param \BookStack\Entities\Models\Entity $entity * @throws \Throwable */ public function buildJointPermissionsForEntity(Entity $entity) @@ -288,7 +286,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); }); @@ -327,7 +325,7 @@ class PermissionService /** * Delete all of the entity jointPermissions for a list of entities. - * @param \BookStack\Entities\Entity[] $entities + * @param \BookStack\Entities\Models\Entity[] $entities * @throws \Throwable */ protected function deleteManyJointPermissionsForEntities($entities) @@ -408,7 +406,7 @@ class PermissionService /** * Get the actions related to an entity. - * @param \BookStack\Entities\Entity $entity + * @param \BookStack\Entities\Models\Entity $entity * @return array */ protected function getActions(Entity $entity) @@ -494,7 +492,7 @@ class PermissionService /** * Create an array of data with the information of an entity jointPermissions. * Used to build data for bulk insertion. - * @param \BookStack\Entities\Entity $entity + * @param \BookStack\Entities\Models\Entity $entity * @param Role $role * @param $action * @param $permissionAll @@ -549,10 +547,43 @@ 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. - * @param \BookStack\Entities\Entity $entity + * @param \BookStack\Entities\Models\Entity $entity * @param $action * @return bool|mixed */ @@ -594,46 +625,46 @@ 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); + }); + }); } /** * Add restrictions for a generic entity * @param string $entityType - * @param Builder|\BookStack\Entities\Entity $query + * @param Builder|\BookStack\Entities\Models\Entity $query * @param string $action * @return Builder */ @@ -642,12 +673,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); }); - } }); } @@ -662,7 +692,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') { @@ -690,18 +720,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) { @@ -719,7 +752,9 @@ class PermissionService }); })->orWhere($tableDetails['entityIdColumn'], '=', 0); }); + $this->clean(); + return $q; }