diff --git a/src/Fieldtypes/Entries.php b/src/Fieldtypes/Entries.php index 293c2b4c58..6b7c5bac11 100644 --- a/src/Fieldtypes/Entries.php +++ b/src/Fieldtypes/Entries.php @@ -134,15 +134,24 @@ protected function configFieldItems(): array public function getIndexItems($request) { $configuredCollections = $this->getConfiguredCollections(); - $requestedCollections = $this->getRequestedCollections($request, $configuredCollections); - $this->authorizeCollectionAccess($requestedCollections); + $this->authorizeCollectionAccess($configuredCollections); $query = $this->getIndexQuery($request); $filters = $request->filters; if (! isset($filters['collection'])) { - $query->whereIn('collection', $configuredCollections); + $user = User::current(); + + $query->whereIn( + 'collection', + collect($configuredCollections) + ->map(fn (string $collectionHandle) => Collection::findByHandle($collectionHandle)) + ->filter() + ->filter(fn ($collection) => $user->can('view', $collection)) + ->map->handle() + ->all() + ); } if ($blueprints = $this->config('blueprints')) { @@ -162,28 +171,16 @@ public function getIndexItems($request) return $paginate ? $results->setCollection($items) : $items; } - private function getRequestedCollections($request, $configuredCollections) - { - $filteredCollections = collect($request->input('filters.collection.collections', [])) - ->filter() - ->values() - ->all(); - - return empty($filteredCollections) ? $configuredCollections : $filteredCollections; - } - - private function authorizeCollectionAccess($collections) + private function authorizeCollectionAccess(array $collections): void { $user = User::current(); - collect($collections)->each(function ($collectionHandle) use ($user) { - $collection = Collection::findByHandle($collectionHandle); + $authorizedCollections = collect($collections) + ->map(fn (string $collectionHandle) => Collection::findByHandle($collectionHandle)) + ->filter() + ->filter(fn ($collection) => $user->can('view', $collection)); - throw_if( - ! $collection || ! $user->can('view', $collection), - new AuthorizationException - ); - }); + throw_if($authorizedCollections->isEmpty(), new AuthorizationException); } public function getResourceCollection($request, $items) diff --git a/src/Fieldtypes/Terms.php b/src/Fieldtypes/Terms.php index 3408672388..684316e1f4 100644 --- a/src/Fieldtypes/Terms.php +++ b/src/Fieldtypes/Terms.php @@ -263,9 +263,7 @@ public function getIndexItems($request) return collect(); } - $this->authorizeTaxonomyAccess( - $this->getRequestedTaxonomies($request, $this->getConfiguredTaxonomies()) - ); + $this->authorizeTaxonomyAccess($this->getConfiguredTaxonomies()); $query = $this->getIndexQuery($request); @@ -276,25 +274,16 @@ public function getIndexItems($request) return $request->boolean('paginate', true) ? $query->paginate() : $query->get(); } - private function getRequestedTaxonomies($request, $configuredTaxonomies) - { - $requestedTaxonomies = collect($request->taxonomies)->filter()->values()->all(); - - return empty($requestedTaxonomies) ? $configuredTaxonomies : $requestedTaxonomies; - } - - private function authorizeTaxonomyAccess($taxonomies) + private function authorizeTaxonomyAccess(array $taxonomies): void { $user = User::current(); - collect($taxonomies)->each(function ($taxonomyHandle) use ($user) { - $taxonomy = Taxonomy::findByHandle($taxonomyHandle); + $authorizedTaxonomies = collect($taxonomies) + ->map(fn (string $taxonomyHandle) => Taxonomy::findByHandle($taxonomyHandle)) + ->filter() + ->filter(fn ($taxonomy) => $user->can('view', $taxonomy)); - throw_if( - ! $taxonomy || ! $user->can('view', $taxonomy), - new AuthorizationException - ); - }); + throw_if($authorizedTaxonomies->isEmpty(), new AuthorizationException); } public function getResourceCollection($request, $items) @@ -434,10 +423,16 @@ protected function getColumns() protected function getIndexQuery($request) { $query = Term::query(); + $user = User::current(); - if ($taxonomies = $request->taxonomies) { - $query->whereIn('taxonomy', $taxonomies); - } + $taxonomies = collect($request->taxonomies ?? $this->getConfiguredTaxonomies()) + ->map(fn (string $taxonomyHandle) => Taxonomy::findByHandle($taxonomyHandle)) + ->filter() + ->filter(fn ($collection) => $user->can('view', $collection)) + ->map->handle() + ->all(); + + $query->whereIn('taxonomy', $taxonomies); if ($search = $request->search) { $query->where('title', 'like', '%'.$search.'%'); diff --git a/src/Query/Scopes/Filters/Collection.php b/src/Query/Scopes/Filters/Collection.php index ddfa4cf9b0..ddfffa612b 100644 --- a/src/Query/Scopes/Filters/Collection.php +++ b/src/Query/Scopes/Filters/Collection.php @@ -2,7 +2,9 @@ namespace Statamic\Query\Scopes\Filters; +use Statamic\Exceptions\AuthorizationException; use Statamic\Facades; +use Statamic\Facades\User; use Statamic\Query\Scopes\Filter; class Collection extends Filter @@ -29,6 +31,8 @@ public function fieldItems() public function apply($query, $values) { + $this->authorizeCollectionAccess($values['collections']); + $query->whereIn('collection', $values['collections']); } @@ -44,8 +48,25 @@ public function visibleTo($key) protected function options() { - return collect($this->context['collections'])->mapWithKeys(function ($collection) { - return [$collection => Facades\Collection::findByHandle($collection)->title()]; + $user = User::current(); + + return collect($this->context['collections']) + ->map(fn ($collection) => Facades\Collection::findByHandle($collection)) + ->filter(fn ($collection) => $user->can('view', $collection)) + ->mapWithKeys(fn ($collection) => [$collection->handle() => $collection->title()]); + } + + private function authorizeCollectionAccess(array $collections): void + { + $user = User::current(); + + collect($collections)->each(function (string $collectionHandle) use ($user) { + $collection = Facades\Collection::findByHandle($collectionHandle); + + throw_if( + ! $collection || ! $user->can('view', $collection), + new AuthorizationException + ); }); } } diff --git a/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php b/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php index 3d9504855c..dba063dba1 100644 --- a/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php +++ b/tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php @@ -61,8 +61,40 @@ public function it_filters_entries_by_query_scopes() } #[Test] - public function it_denies_access_to_entries_when_theres_a_collection_the_user_cannot_view() + public function it_limits_access_to_entries_from_collections_the_user_can_view() { + Collection::make('pages')->save(); + Entry::make()->collection('pages')->slug('home')->data(['title' => 'Home'])->save(); + + Collection::make('secret')->save(); + Entry::make()->collection('secret')->slug('secret-one')->data(['title' => 'Secret One'])->save(); + + $this->setTestRoles(['test' => ['access cp', 'view pages entries']]); + $user = User::make()->assignRole('test')->save(); + + $config = base64_encode(json_encode([ + 'type' => 'entries', + 'collections' => ['pages', 'secret'], + ])); + + $this + ->actingAs($user) + ->getJson("/cp/fieldtypes/relationship?config={$config}") + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJson([ + 'data' => [ + ['slug' => 'home'], + ], + ]); + } + + #[Test] + public function it_denies_access_to_entries_when_user_cannot_view_any_of_the_collections() + { + Collection::make('pages')->save(); + Entry::make()->collection('pages')->slug('home')->data(['title' => 'Home'])->save(); + Collection::make('secret')->save(); Entry::make()->collection('secret')->slug('secret-one')->data(['title' => 'Secret One'])->save(); @@ -71,7 +103,7 @@ public function it_denies_access_to_entries_when_theres_a_collection_the_user_ca $config = base64_encode(json_encode([ 'type' => 'entries', - 'collections' => ['secret'], + 'collections' => ['pages', 'secret'], ])); $this @@ -81,8 +113,11 @@ public function it_denies_access_to_entries_when_theres_a_collection_the_user_ca } #[Test] - public function it_forbids_access_to_entries_when_filters_target_a_collection_the_user_cannot_view() + public function it_forbids_access_to_entries_when_filters_target_collections_the_user_cannot_view() { + Collection::make('pages')->save(); + Entry::make()->collection('pages')->slug('home')->data(['title' => 'Home'])->save(); + Collection::make('secret')->save(); Entry::make()->collection('test')->slug('apple')->data(['title' => 'Apple'])->save(); Entry::make()->collection('secret')->slug('secret-one')->data(['title' => 'Secret One'])->save(); @@ -107,46 +142,52 @@ public function it_forbids_access_to_entries_when_filters_target_a_collection_th } #[Test] - public function it_forbids_access_to_terms_when_config_contains_a_taxonomy_the_user_cannot_view() + public function it_limits_access_to_terms_from_taxonomies_the_user_can_view() { + Taxonomy::make('topics')->save(); Taxonomy::make('secret')->save(); + Term::make('public')->taxonomy('topics')->data([])->save(); Term::make('internal')->taxonomy('secret')->data([])->save(); - $this->setTestRoles(['test' => ['access cp']]); + $this->setTestRoles(['test' => ['access cp', 'view topics terms']]); $user = User::make()->assignRole('test')->save(); $config = base64_encode(json_encode([ 'type' => 'terms', - 'taxonomies' => ['secret'], + 'taxonomies' => ['topics', 'secret'], ])); $this ->actingAs($user) - ->getJson("/cp/fieldtypes/relationship?config={$config}&taxonomies[0]=secret") - ->assertForbidden(); + ->getJson("/cp/fieldtypes/relationship?config={$config}&taxonomies[0]=topics&taxonomies[1]=secret") + ->assertOk() + ->assertJsonCount(1, 'data') + ->assertJson([ + 'data' => [ + ['slug' => 'public'], + ], + ]); } #[Test] - public function it_forbids_access_to_terms_when_requested_taxonomy_is_forbidden() + public function it_forbids_access_to_terms_when_the_user_cannot_view_any_of_the_taxonomies() { Taxonomy::make('topics')->save(); Taxonomy::make('secret')->save(); Term::make('public')->taxonomy('topics')->data([])->save(); Term::make('internal')->taxonomy('secret')->data([])->save(); - $this->setTestRoles([ - 'test' => ['access cp', 'view topics terms'], - ]); + $this->setTestRoles(['test' => ['access cp']]); $user = User::make()->assignRole('test')->save(); $config = base64_encode(json_encode([ 'type' => 'terms', - 'taxonomies' => ['topics'], + 'taxonomies' => ['topics', 'secret'], ])); $this ->actingAs($user) - ->getJson("/cp/fieldtypes/relationship?config={$config}&taxonomies[0]=secret") + ->getJson("/cp/fieldtypes/relationship?config={$config}&taxonomies[0]=topics&taxonomies[1]=secret") ->assertForbidden(); } }