X-Git-Url: http://source.bookstackapp.com/bookstack/blobdiff_plain/1e5951a75f053c3c45e6e56a96e76dbd79807d99..refs/pull/3391/head:/app/Auth/Permissions/PermissionService.php diff --git a/app/Auth/Permissions/PermissionService.php b/app/Auth/Permissions/PermissionService.php index 57b4a6bd6..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; } @@ -495,24 +514,21 @@ class PermissionService $userRoleIds = $this->currentUser()->roles()->select('id')->pluck('id')->toArray(); $userId = $this->currentUser()->id; - $permissionQuery = $this->db->table('joint_permissions') + $permissionQuery = JointPermission::query() ->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('owned_by', '=', $userId); - }); + ->where(function (Builder $query) use ($userId) { + $this->addJointHasPermissionCheck($query, $userId); }); if (!is_null($entityClass)) { - $entityInstance = app()->make($entityClass); + $entityInstance = app($entityClass); $permissionQuery = $permissionQuery->where('entity_type', '=', $entityInstance->getMorphClass()); } $hasPermission = $permissionQuery->count() > 0; $this->clean(); + return $hasPermission; } @@ -526,17 +542,14 @@ class PermissionService $parentQuery->whereHas('jointPermissions', function ($permissionQuery) use ($action) { $permissionQuery->whereIn('role_id', $this->getCurrentUserRoles()) ->where('action', '=', $action) - ->where(function ($query) { - $query->where('has_permission', '=', true) - ->orWhere(function ($query) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $this->currentUser()->id); - }); + ->where(function (Builder $query) { + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); }); }); }); $this->clean(); + return $q; } @@ -547,16 +560,13 @@ 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()) ->where('action', '=', $ability) ->where(function (Builder $query) { - $query->where('has_permission', '=', true) - ->orWhere(function (Builder $query) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $this->currentUser()->id); - }); + $this->addJointHasPermissionCheck($query, $this->currentUser()->id); }); }); }); @@ -580,17 +590,11 @@ class PermissionService /** * Add restrictions for a generic entity. */ - public function enforceEntityRestrictions(string $entityType, Builder $query, string $action = 'view'): Builder + public function enforceEntityRestrictions(Entity $entity, Builder $query, string $action = 'view'): Builder { - if (strtolower($entityType) === 'page') { + if ($entity instanceof Page) { // Prevent drafts being visible to others. - $query->where(function ($query) { - $query->where('draft', '=', false) - ->orWhere(function ($query) { - $query->where('draft', '=', true) - ->where('owned_by', '=', $this->currentUser()->id); - }); - }); + $this->enforceDraftVisibilityOnQuery($query); } return $this->entityRestrictionQuery($query, $action); @@ -598,28 +602,39 @@ class PermissionService /** * Filter items that have entities set as a polymorphic relation. + * 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(Builder $query, string $tableName, string $entityIdColumn, string $entityTypeColumn, string $action = 'view'): Builder + 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 ($query) { - $query->where('has_permission', '=', true)->orWhere(function ($query) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $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; } @@ -629,33 +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 ($query) { - $query->where('has_permission', '=', true)->orWhere(function ($query) { - $query->where('has_permission_own', '=', true) - ->where('owned_by', '=', $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; } /** - * Get the current user + * 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('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. */ private function currentUser(): User {