Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 18 additions & 21 deletions src/Fieldtypes/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')) {
Expand All @@ -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)
Expand Down
37 changes: 16 additions & 21 deletions src/Fieldtypes/Terms.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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)
Expand Down Expand Up @@ -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.'%');
Expand Down
25 changes: 23 additions & 2 deletions src/Query/Scopes/Filters/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,6 +31,8 @@ public function fieldItems()

public function apply($query, $values)
{
$this->authorizeCollectionAccess($values['collections']);

$query->whereIn('collection', $values['collections']);
}

Expand All @@ -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
);
});
}
}
69 changes: 55 additions & 14 deletions tests/Feature/Fieldtypes/RelationshipFieldtypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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();
}
}
Expand Down
Loading