X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/3de02566bf199f3a49fcfe2b2d4119a287eb26bb..refs/pull/3138/head:/app/Auth/Permissions/PermissionService.php diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 1c82f8e08..59ff37dc9 100644 --- a/app/Auth/Permissions/PermissionService.php +++ b/app/Auth/Permissions/PermissionService.php @@ -1,4 +1,6 @@ - function ($query) { $query->withTrashed()->select(['id', 'restricted', 'owned_by', 'book_id', 'chapter_id']); - } + }, ]); } /** * Build joint permissions for the given shelf and role combinations. + * * @throws Throwable */ protected function buildJointPermissionsForShelves(EloquentCollection $shelves, array $roles, bool $deleteOld = false) @@ -169,6 +173,7 @@ class PermissionService /** * Build joint permissions for the given book and role combinations. + * * @throws Throwable */ protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false) @@ -193,6 +198,7 @@ class PermissionService /** * Rebuild the entity jointPermissions for a particular entity. + * * @throws Throwable */ public function buildJointPermissionsForEntity(Entity $entity) @@ -201,6 +207,7 @@ class PermissionService if ($entity instanceof Book) { $books = $this->bookFetchQuery()->where('id', '=', $entity->id)->get(); $this->buildJointPermissionsForBooks($books, Role::query()->get()->all(), true); + return; } @@ -224,6 +231,7 @@ class PermissionService /** * Rebuild the entity jointPermissions for a collection of entities. + * * @throws Throwable */ public function buildJointPermissionsForEntities(array $entities) @@ -263,6 +271,7 @@ class PermissionService /** * Delete all of the entity jointPermissions for a list of entities. + * * @param Role[] $roles */ protected function deleteManyJointPermissionsForRoles($roles) @@ -275,7 +284,9 @@ class PermissionService /** * Delete the entity jointPermissions for a particular entity. + * * @param Entity $entity + * * @throws Throwable */ public function deleteJointPermissionsForEntity(Entity $entity) @@ -285,7 +296,9 @@ class PermissionService /** * Delete all of the entity jointPermissions for a list of entities. + * * @param Entity[] $entities + * * @throws Throwable */ protected function deleteManyJointPermissionsForEntities(array $entities) @@ -295,7 +308,6 @@ class PermissionService } $this->db->transaction(function () use ($entities) { - foreach (array_chunk($entities, 1000) as $entityChunk) { $query = $this->db->table('joint_permissions'); foreach ($entityChunk as $entity) { @@ -311,8 +323,10 @@ class PermissionService /** * Create & Save entity jointPermissions for many entities and roles. + * * @param Entity[] $entities - * @param Role[] $roles + * @param Role[] $roles + * * @throws Throwable */ protected function createManyJointPermissions(array $entities, array $roles) @@ -363,7 +377,6 @@ class PermissionService }); } - /** * Get the actions related to an entity. */ @@ -376,6 +389,7 @@ class PermissionService if ($entity instanceof Book) { $baseActions[] = 'chapter-create'; } + return $baseActions; } @@ -397,6 +411,7 @@ class PermissionService if ($entity->restricted) { $hasAccess = $this->mapHasActiveRestriction($permissionMap, $entity, $role, $restrictionAction); + return $this->createJointPermissionDataArray($entity, $role, $action, $hasAccess, $hasAccess); } @@ -433,6 +448,7 @@ class PermissionService protected function mapHasActiveRestriction(array $entityMap, Entity $entity, Role $role, string $action): bool { $key = $entity->getMorphClass() . ':' . $entity->getRawAttribute('id') . ':' . $role->getRawAttribute('id') . ':' . $action; + return $entityMap[$key] ?? false; } @@ -443,18 +459,19 @@ class PermissionService protected function createJointPermissionDataArray(Entity $entity, Role $role, string $action, bool $permissionAll, bool $permissionOwn): array { return [ - 'role_id' => $role->getRawAttribute('id'), - 'entity_id' => $entity->getRawAttribute('id'), - 'entity_type' => $entity->getMorphClass(), - 'action' => $action, - 'has_permission' => $permissionAll, + 'role_id' => $role->getRawAttribute('id'), + 'entity_id' => $entity->getRawAttribute('id'), + 'entity_type' => $entity->getMorphClass(), + 'action' => $action, + 'has_permission' => $permissionAll, 'has_permission_own' => $permissionOwn, - 'owned_by' => $entity->getRawAttribute('owned_by'), + 'owned_by' => $entity->getRawAttribute('owned_by'), ]; } /** * Checks if an entity has a restriction set upon it. + * * @param HasCreatorAndUpdater|HasOwner $ownable */ public function checkOwnableUserAccess(Model $ownable, string $permission): bool @@ -473,7 +490,8 @@ class PermissionService $ownPermission = $user && $user->can($permission . '-own'); $ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by'; $isOwner = $user && $user->id === $ownable->$ownerField; - return ($allPermission || ($isOwner && $ownPermission)); + + return $allPermission || ($isOwner && $ownPermission); } // Handle abnormal create jointPermissions @@ -483,6 +501,7 @@ class PermissionService $hasAccess = $this->entityRestrictionQuery($baseQuery, $action)->count() > 0; $this->clean(); + return $hasAccess; } @@ -509,6 +528,7 @@ class PermissionService $hasPermission = $permissionQuery->count() > 0; $this->clean(); + return $hasPermission; } @@ -529,6 +549,7 @@ class PermissionService }); $this->clean(); + return $q; } @@ -539,6 +560,7 @@ class PermissionService public function restrictEntityQuery(Builder $query, string $ability = 'view'): Builder { $this->clean(); + return $query->where(function (Builder $parentQuery) use ($ability) { $parentQuery->whereHas('jointPermissions', function (Builder $permissionQuery) use ($ability) { $permissionQuery->whereIn('role_id', $this->getCurrentUserRoles()) @@ -580,26 +602,39 @@ class PermissionService /** * Filter items that have entities set as a polymorphic relation. - * @param Builder|\Illuminate\Database\Query\Builder $query + * For simplicity, this will not return results attached to draft pages. + * Draft pages should never really have related items though. + * + * @param Builder|QueryBuilder $query */ public function filterRestrictedEntityRelations($query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view') { $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn, 'entityTypeColumn' => $entityTypeColumn]; - - $q = $query->where(function ($query) use ($tableDetails, $action) { - $query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) { - $permissionQuery->select('id')->from('joint_permissions') - ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) - ->whereRaw('joint_permissions.entity_type=' . $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) - ->where('action', '=', $action) - ->whereIn('role_id', $this->getCurrentUserRoles()) - ->where(function (QueryBuilder $query) { - $this->addJointHasPermissionCheck($query, $this->currentUser()->id); - }); - }); + $pageMorphClass = (new Page())->getMorphClass(); + + $q = $query->whereExists(function ($permissionQuery) use (&$tableDetails, $action) { + /** @var Builder $permissionQuery */ + $permissionQuery->select(['role_id'])->from('joint_permissions') + ->whereColumn('joint_permissions.entity_id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) + ->whereColumn('joint_permissions.entity_type', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn']) + ->where('joint_permissions.action', '=', $action) + ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoles()) + ->where(function (QueryBuilder $query) { + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); + }); + })->where(function ($query) use ($tableDetails, $pageMorphClass) { + /** @var Builder $query */ + $query->where($tableDetails['entityTypeColumn'], '!=', $pageMorphClass) + ->orWhereExists(function (QueryBuilder $query) use ($tableDetails, $pageMorphClass) { + $query->select('id')->from('pages') + ->whereColumn('pages.id', '=', $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) + ->where($tableDetails['tableName'] . '.' . $tableDetails['entityTypeColumn'], '=', $pageMorphClass) + ->where('pages.draft', '=', false); + }); }); $this->clean(); + return $q; } @@ -609,43 +644,60 @@ class PermissionService */ public function filterRelatedEntity(string $entityClass, Builder $query, string $tableName, string $entityIdColumn): Builder { - $tableDetails = ['tableName' => $tableName, 'entityIdColumn' => $entityIdColumn]; - $morphClass = app($entityClass)->getMorphClass(); - - $q = $query->where(function ($query) use ($tableDetails, $morphClass) { - $query->where(function ($query) use (&$tableDetails, $morphClass) { - $query->whereExists(function ($permissionQuery) use (&$tableDetails, $morphClass) { - $permissionQuery->select('id')->from('joint_permissions') - ->whereRaw('joint_permissions.entity_id=' . $tableDetails['tableName'] . '.' . $tableDetails['entityIdColumn']) - ->where('entity_type', '=', $morphClass) - ->where('action', '=', 'view') - ->whereIn('role_id', $this->getCurrentUserRoles()) - ->where(function (QueryBuilder $query) { - $this->addJointHasPermissionCheck($query, $this->currentUser()->id); - }); + $fullEntityIdColumn = $tableName . '.' . $entityIdColumn; + $instance = new $entityClass(); + $morphClass = $instance->getMorphClass(); + + $existsQuery = function ($permissionQuery) use ($fullEntityIdColumn, $morphClass) { + /** @var Builder $permissionQuery */ + $permissionQuery->select('joint_permissions.role_id')->from('joint_permissions') + ->whereColumn('joint_permissions.entity_id', '=', $fullEntityIdColumn) + ->where('joint_permissions.entity_type', '=', $morphClass) + ->where('joint_permissions.action', '=', 'view') + ->whereIn('joint_permissions.role_id', $this->getCurrentUserRoles()) + ->where(function (QueryBuilder $query) { + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); }); - })->orWhere($tableDetails['entityIdColumn'], '=', 0); + }; + + $q = $query->where(function ($query) use ($existsQuery, $fullEntityIdColumn) { + $query->whereExists($existsQuery) + ->orWhere($fullEntityIdColumn, '=', 0); }); + if ($instance instanceof Page) { + // Prevent visibility of non-owned draft pages + $q->whereExists(function (QueryBuilder $query) use ($fullEntityIdColumn) { + $query->select('id')->from('pages') + ->whereColumn('pages.id', '=', $fullEntityIdColumn) + ->where(function (QueryBuilder $query) { + $query->where('pages.draft', '=', false) + ->orWhere('pages.owned_by', '=', $this->currentUser()->id); + }); + }); + } + $this->clean(); + return $q; } /** * Add the query for checking the given user id has permission * within the join_permissions table. + * * @param QueryBuilder|Builder $query */ protected function addJointHasPermissionCheck($query, int $userIdToCheck) { - $query->where('has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $userIdToCheck); + $query->where('joint_permissions.has_permission', '=', true)->orWhere(function ($query) use ($userIdToCheck) { + $query->where('joint_permissions.has_permission_own', '=', true) + ->where('joint_permissions.owned_by', '=', $userIdToCheck); }); } /** - * Get the current user + * Get the current user. */ private function currentUser(): User {