From 22ca7e98cf3ff4bdbba482a66db2428c5b7fb322 Mon Sep 17 00:00:00 2001 From: markus-moser Date: Mon, 27 May 2024 12:19:06 +0200 Subject: [PATCH] Implement permission checks --- .../search-index-adapter/open-search.yaml | 3 + config/services/search/search-services.yaml | 7 +- .../Workspaces/ElementWorkspacesQuery.php | 40 +++++++++ .../Workspace/WorkspaceQueryHandler.php | 22 ++++- .../ElementWorkspacesQueryService.php | 82 +++++++++++++++++++ ...ElementWorkspacesQueryServiceInterface.php | 18 ++++ .../Permission/UserPermissionService.php | 2 +- .../SearchService/AbstractSearchHelper.php | 33 +------- .../Element/ElementSearchHelperInterface.php | 20 +++++ .../Element/ElementSearchService.php | 14 ++-- .../SearchService/Element/SearchHelper.php | 47 +++++++---- .../Traits/SearchHelperTrait.php | 45 ++++++++++ 12 files changed, 273 insertions(+), 60 deletions(-) create mode 100644 src/Model/Search/Modifier/Filter/Workspaces/ElementWorkspacesQuery.php create mode 100644 src/SearchIndexAdapter/OpenSearch/Workspace/ElementWorkspacesQueryService.php create mode 100644 src/SearchIndexAdapter/Workspace/ElementWorkspacesQueryServiceInterface.php create mode 100644 src/Service/Search/SearchService/Element/ElementSearchHelperInterface.php create mode 100644 src/Service/Search/SearchService/Traits/SearchHelperTrait.php diff --git a/config/services/search-index-adapter/open-search.yaml b/config/services/search-index-adapter/open-search.yaml index a98a7f84..314f1d5b 100644 --- a/config/services/search-index-adapter/open-search.yaml +++ b/config/services/search-index-adapter/open-search.yaml @@ -53,6 +53,9 @@ services: Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Workspace\QueryServiceInterface: class: Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\OpenSearch\Workspace\QueryService + Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Workspace\ElementWorkspacesQueryServiceInterface: + class: Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\OpenSearch\Workspace\ElementWorkspacesQueryService + Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\QueryLanguage\PqlAdapterInterface: class: Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\OpenSearch\QueryLanguage\PqlAdapter diff --git a/config/services/search/search-services.yaml b/config/services/search/search-services.yaml index 9ebbc1a1..99e4ebde 100644 --- a/config/services/search/search-services.yaml +++ b/config/services/search/search-services.yaml @@ -11,15 +11,16 @@ services: class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\DataObject\DataObjectSearchService Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Document\DocumentSearchServiceInterface: - class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Document\DocumentSearchService + class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Document\DocumentSearchService Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\ElementSearchServiceInterface: - class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\ElementSearchService + class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\ElementSearchService Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\DataObject\SearchHelper: ~ Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Asset\SearchHelper: ~ Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Document\SearchHelper: ~ - Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\SearchHelper: ~ + Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\ElementSearchHelperInterface: + class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element\SearchHelper Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\SearchProviderInterface: class: Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\SearchProvider diff --git a/src/Model/Search/Modifier/Filter/Workspaces/ElementWorkspacesQuery.php b/src/Model/Search/Modifier/Filter/Workspaces/ElementWorkspacesQuery.php new file mode 100644 index 00000000..d3dc037e --- /dev/null +++ b/src/Model/Search/Modifier/Filter/Workspaces/ElementWorkspacesQuery.php @@ -0,0 +1,40 @@ +user; + } + + public function getPermission(): ?string + { + return $this->permission ?? PermissionTypes::LIST->value; + } +} diff --git a/src/SearchIndexAdapter/OpenSearch/Search/Modifier/Filter/Workspace/WorkspaceQueryHandler.php b/src/SearchIndexAdapter/OpenSearch/Search/Modifier/Filter/Workspace/WorkspaceQueryHandler.php index 05376529..ac0cfb90 100644 --- a/src/SearchIndexAdapter/OpenSearch/Search/Modifier/Filter/Workspace/WorkspaceQueryHandler.php +++ b/src/SearchIndexAdapter/OpenSearch/Search/Modifier/Filter/Workspace/WorkspaceQueryHandler.php @@ -18,7 +18,9 @@ use Pimcore\Bundle\GenericDataIndexBundle\Attribute\OpenSearch\AsSearchModifierHandler; use Pimcore\Bundle\GenericDataIndexBundle\Model\OpenSearch\Modifier\SearchModifierContextInterface; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Workspaces\ElementWorkspacesQuery; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Workspaces\WorkspaceQuery; +use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Workspace\ElementWorkspacesQueryServiceInterface; use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Workspace\QueryServiceInterface; /** @@ -27,7 +29,8 @@ final readonly class WorkspaceQueryHandler { public function __construct( - private QueryServiceInterface $workspaceQueryService + private QueryServiceInterface $workspaceQueryService, + private ElementWorkspacesQueryServiceInterface $elementWorkspacesQueryService ) { } @@ -48,4 +51,21 @@ public function handleWorkspaceQuery( ) ); } + + #[AsSearchModifierHandler] + public function handleElementWorkspacesQuery( + ElementWorkspacesQuery $elementWorkspacesQuery, + SearchModifierContextInterface $context + ): void { + if (!$elementWorkspacesQuery->getUser()) { + return; + } + + $context->getSearch()->addQuery( + $this->elementWorkspacesQueryService->getWorkspaceQuery( + user: $elementWorkspacesQuery->getUser(), + permission: $elementWorkspacesQuery->getPermission() + ) + ); + } } diff --git a/src/SearchIndexAdapter/OpenSearch/Workspace/ElementWorkspacesQueryService.php b/src/SearchIndexAdapter/OpenSearch/Workspace/ElementWorkspacesQueryService.php new file mode 100644 index 00000000..d547d098 --- /dev/null +++ b/src/SearchIndexAdapter/OpenSearch/Workspace/ElementWorkspacesQueryService.php @@ -0,0 +1,82 @@ + UserPermissionTypes::ASSETS->value, + DataObjectWorkspace::WORKSPACE_TYPE => UserPermissionTypes::OBJECTS->value, + DocumentWorkspace::WORKSPACE_TYPE => UserPermissionTypes::DOCUMENTS->value, + ]; + + public function __construct( + private QueryServiceInterface $workspaceQueryService, + private UserPermissionServiceInterface $userPermissionService + ) { + } + + public function getWorkspaceQuery(?User $user, string $permission): BoolQuery + { + $boolQuery = new BoolQuery(); + if ($user?->isAdmin()) { + return $boolQuery; + } + + $boolQuery->addCondition( + ConditionType::FILTER->value, + $this->getAllowedElementTypesFilter($user)->toArrayAsSubQuery() + ); + + $workspacesQuery = new BoolQuery(); + foreach (array_keys(self::WORKSPACE_PERMISSION_MAPPING) as $workspaceType) { + $workspaceQuery = new BoolQuery([ + ConditionType::FILTER->value => [ + new TermFilter( + SystemField::ELEMENT_TYPE->getPath(), ElementType::fromShortValue($workspaceType)->value + ), + $this->workspaceQueryService->getWorkspaceQuery($workspaceType, $user, $permission) + ] + ]); + + $workspacesQuery->addCondition(ConditionType::SHOULD->value, $workspaceQuery->toArray(true)); + } + + if (!$workspacesQuery->isEmpty()) { + $boolQuery->addCondition(ConditionType::MUST->value, $workspacesQuery->toArray(true)); + } + + return $boolQuery; + } + + private function getAllowedElementTypesFilter(User $user): TermsFilter + { + $allowedElementTypes = []; + foreach (self::WORKSPACE_PERMISSION_MAPPING as $workspaceType => $permission) { + if ($this->userPermissionService->hasPermission($user, $permission)) { + $allowedElementTypes[] = ElementType::fromShortValue($workspaceType)->value; + } + } + + return new TermsFilter(SystemField::ELEMENT_TYPE->getPath(), $allowedElementTypes); + } +} \ No newline at end of file diff --git a/src/SearchIndexAdapter/Workspace/ElementWorkspacesQueryServiceInterface.php b/src/SearchIndexAdapter/Workspace/ElementWorkspacesQueryServiceInterface.php new file mode 100644 index 00000000..05b7b5c2 --- /dev/null +++ b/src/SearchIndexAdapter/Workspace/ElementWorkspacesQueryServiceInterface.php @@ -0,0 +1,18 @@ +hasPermission($user, $userPermission)) { - throw new UserPermissionException('User does not have permission to view assets'); + throw new UserPermissionException('User does not have permission to view ' . $userPermission); } } diff --git a/src/Service/Search/SearchService/AbstractSearchHelper.php b/src/Service/Search/SearchService/AbstractSearchHelper.php index 37e2b753..96520ccf 100644 --- a/src/Service/Search/SearchService/AbstractSearchHelper.php +++ b/src/Service/Search/SearchService/AbstractSearchHelper.php @@ -27,6 +27,7 @@ use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Search\Modifier\SearchModifierServiceInterface; use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\SearchIndexServiceInterface; use Pimcore\Bundle\GenericDataIndexBundle\Service\Permission\UserPermissionServiceInterface; +use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Traits\SearchHelperTrait; use Pimcore\Model\User; /** @@ -34,6 +35,8 @@ */ abstract class AbstractSearchHelper implements SearchHelperInterface { + use SearchHelperTrait; + public function __construct( private readonly SearchIndexServiceInterface $searchIndexService, private readonly SearchModifierServiceInterface $searchModifierService, @@ -63,22 +66,6 @@ public function addSearchRestrictions( return $search; } - public function performSearch(SearchInterface $search, string $indexName): SearchResult - { - $adapterSearch = $this->searchIndexService->createPaginatedSearch( - $search->getPage(), - $search->getPageSize(), - $search->isAggregationsOnly() - ); - - $search->addModifier(new OrderByPageNumber($indexName, $search)); - $this->searchModifierService->applyModifiersFromSearch($search, $adapterSearch); - - return $this - ->searchIndexService - ->search($adapterSearch, $indexName); - } - /** * @return int[] */ @@ -113,20 +100,6 @@ public function getChildrenCounts( return $childrenCounts; } - public function hydrateSearchResultHits( - SearchResult $searchResult, - array $childrenCounts, - ?User $user = null - ): array { - $results = []; - - foreach ($searchResult->getHits() as $hit) { - $results[] = $this->hydrateSearchResultHit($hit, $childrenCounts, $user); - } - - return $results; - } - abstract public function hydrateSearchResultHit( SearchResultHit $searchResultHit, array $childrenCounts, diff --git a/src/Service/Search/SearchService/Element/ElementSearchHelperInterface.php b/src/Service/Search/SearchService/Element/ElementSearchHelperInterface.php new file mode 100644 index 00000000..a9637f82 --- /dev/null +++ b/src/Service/Search/SearchService/Element/ElementSearchHelperInterface.php @@ -0,0 +1,20 @@ +searchHelper->addSearchRestrictions( - search: $elementSearch, - userPermission: UserPermissionTypes::DOCUMENTS->value, - workspaceType: DocumentWorkspace::WORKSPACE_TYPE - );*/ + $elementSearch = $this->searchHelper->addSearchRestrictions($elementSearch); $searchResult = $this->searchHelper->performSearch( - search: $elementSearch, - indexName: $this->globalIndexAliasService->getElementSearchAliasName() + $elementSearch, + $this->globalIndexAliasService->getElementSearchAliasName() ); try { @@ -84,7 +80,7 @@ public function byId(ElementType $elementType, int $id, ?User $user = null): ?El ElementType::ASSET => $this->assetSearchService->byId($id, $user), ElementType::DATA_OBJECT => $this->dataObjectSearchService->byId($id, $user), }; - } catch(Exception $e) { + } catch (Exception $e) { throw new ElementSearchException($e->getMessage(), 0, $e); } diff --git a/src/Service/Search/SearchService/Element/SearchHelper.php b/src/Service/Search/SearchService/Element/SearchHelper.php index 00611524..ecb536fc 100644 --- a/src/Service/Search/SearchService/Element/SearchHelper.php +++ b/src/Service/Search/SearchService/Element/SearchHelper.php @@ -16,39 +16,54 @@ namespace Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\Element; +use Pimcore\Bundle\GenericDataIndexBundle\Enum\Permission\PermissionTypes; use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\ElementType; use Pimcore\Bundle\GenericDataIndexBundle\Enum\SearchIndex\FieldCategory\SystemField; use Pimcore\Bundle\GenericDataIndexBundle\Exception\InvalidArgumentException; use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\ElementSearchResultItemInterface; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Interfaces\SearchInterface; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Filter\Workspaces\ElementWorkspacesQuery; +use Pimcore\Bundle\GenericDataIndexBundle\Model\Search\Modifier\Sort\OrderByPageNumber; +use Pimcore\Bundle\GenericDataIndexBundle\Model\SearchIndexAdapter\SearchResult; use Pimcore\Bundle\GenericDataIndexBundle\Model\SearchIndexAdapter\SearchResultHit; use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\Search\Modifier\SearchModifierServiceInterface; use Pimcore\Bundle\GenericDataIndexBundle\SearchIndexAdapter\SearchIndexServiceInterface; -use Pimcore\Bundle\GenericDataIndexBundle\Service\Permission\UserPermissionServiceInterface; use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService; -use Pimcore\Bundle\GenericDataIndexBundle\Service\Search\SearchService\AbstractSearchHelper; use Pimcore\Model\User; /** * @internal */ -final class SearchHelper extends AbstractSearchHelper +final readonly class SearchHelper implements ElementSearchHelperInterface { + use SearchService\Traits\SearchHelperTrait; + public function __construct( - private readonly SearchService\Asset\SearchHelper $assetSearchHelper, - private readonly SearchService\Document\SearchHelper $documentSearchHelper, - private readonly SearchService\DataObject\SearchHelper $dataObjecttSearchHelper, - private readonly SearchIndexServiceInterface $searchIndexService, - private readonly SearchModifierServiceInterface $searchModifierService, - private readonly UserPermissionServiceInterface $userPermissionService, + private SearchService\Asset\SearchHelper $assetSearchHelper, + private SearchService\Document\SearchHelper $documentSearchHelper, + private SearchService\DataObject\SearchHelper $dataObjectSearchHelper, + private SearchIndexServiceInterface $searchIndexService, + private SearchModifierServiceInterface $searchModifierService, ) { - parent::__construct( - $this->searchIndexService, - $this->searchModifierService, - $this->userPermissionService - ); } - public function hydrateSearchResultHit( + public function addSearchRestrictions(SearchInterface $search): SearchInterface { + $user = $search->getUser(); + if (!$user) { + return $search; + } + + if (!$user->isAdmin()) { + $search->addModifier(new ElementWorkspacesQuery( + $user, + PermissionTypes::LIST->value + )); + } + + return $search; + } + + private function hydrateSearchResultHit( SearchResultHit $searchResultHit, array $childrenCounts, ?User $user = null @@ -56,7 +71,7 @@ public function hydrateSearchResultHit( $elementType = SystemField::ELEMENT_TYPE->getData($searchResultHit->getSource()); return match($elementType) { - ElementType::DATA_OBJECT->value => $this->dataObjecttSearchHelper->hydrateSearchResultHit( + ElementType::DATA_OBJECT->value => $this->dataObjectSearchHelper->hydrateSearchResultHit( $searchResultHit, $childrenCounts, $user diff --git a/src/Service/Search/SearchService/Traits/SearchHelperTrait.php b/src/Service/Search/SearchService/Traits/SearchHelperTrait.php new file mode 100644 index 00000000..306c8387 --- /dev/null +++ b/src/Service/Search/SearchService/Traits/SearchHelperTrait.php @@ -0,0 +1,45 @@ +searchIndexService->createPaginatedSearch( + $search->getPage(), + $search->getPageSize(), + $search->isAggregationsOnly() + ); + + $search->addModifier(new OrderByPageNumber($indexName, $search)); + $this->searchModifierService->applyModifiersFromSearch($search, $adapterSearch); + + return $this + ->searchIndexService + ->search($adapterSearch, $indexName); + } + + public function hydrateSearchResultHits( + SearchResult $searchResult, + array $childrenCounts, + ?User $user = null + ): array { + $results = []; + + foreach ($searchResult->getHits() as $hit) { + $results[] = $this->hydrateSearchResultHit($hit, $childrenCounts, $user); + } + + return $results; + } +} \ No newline at end of file