From 8de78083d3275ed105d4cf2451846606a2d020de Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 29 May 2024 17:28:36 +0100 Subject: [PATCH 01/13] Ensure draft entries are accessible using Live Preview --- src/Http/Controllers/API/ApiController.php | 8 +++++++- .../API/CollectionEntriesController.php | 5 +++-- tests/API/APITest.php | 20 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index c7c959fe77..edfc0d9a70 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -3,6 +3,7 @@ namespace Statamic\Http\Controllers\API; use Facades\Statamic\API\ResourceAuthorizer; +use Illuminate\Http\Request; use Statamic\Exceptions\ApiValidationException; use Statamic\Exceptions\NotFoundHttpException; use Statamic\Facades\Site; @@ -21,10 +22,15 @@ class ApiController extends Controller /** * Abort if item is unpublished. * + * @param \Illuminate\Http\Request $request * @param mixed $item */ - protected function abortIfUnpublished($item) + protected function abortIfUnpublished(Request $request, $item) { + if ($request->statamicToken()) { + return; + } + throw_if($item->published() === false, new NotFoundHttpException); } diff --git a/src/Http/Controllers/API/CollectionEntriesController.php b/src/Http/Controllers/API/CollectionEntriesController.php index 892d823c96..3ebd339d7f 100644 --- a/src/Http/Controllers/API/CollectionEntriesController.php +++ b/src/Http/Controllers/API/CollectionEntriesController.php @@ -3,6 +3,7 @@ namespace Statamic\Http\Controllers\API; use Facades\Statamic\API\FilterAuthorizer; +use Illuminate\Http\Request; use Statamic\Exceptions\NotFoundHttpException; use Statamic\Facades\Entry; use Statamic\Http\Resources\API\EntryResource; @@ -33,14 +34,14 @@ public function index($collection) ); } - public function show($collection, $handle) + public function show(Request $request, $collection, $handle) { $this->abortIfDisabled(); $entry = Entry::find($handle); $this->abortIfInvalid($entry, $collection); - $this->abortIfUnpublished($entry); + $this->abortIfUnpublished($request, $entry); return app(EntryResource::class)::make($entry); } diff --git a/tests/API/APITest.php b/tests/API/APITest.php index b9577ea360..a0b68dc608 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -432,6 +432,26 @@ public function it_replaces_entries_using_live_preview_token() ]); } + /** @test */ + public function live_preview_token_bypasses_entry_status_check() + { + Facades\Config::set('statamic.api.resources.collections', true); + Facades\Collection::make('pages')->save(); + $entry = tap(Facades\Entry::make()->collection('pages')->id('dance')->published(false)->set('title', 'Dance')->slug('dance'))->save(); + + $this->get('/api/collections/pages/entries/dance')->assertJson([ + 'message' => 'Not found.', + ]); + + LivePreview::tokenize('test-token', $entry); + + $this->get('/api/collections/pages/entries/dance?token=test-token')->assertJson([ + 'data' => [ + 'title' => 'Dance', + ], + ]); + } + /** @test */ public function it_replaces_terms_using_live_preview_token() { From f019401ac7aecda12c3b051e756b13753312627b Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Wed, 29 May 2024 18:46:34 +0100 Subject: [PATCH 02/13] Allow viewing draft entries using `draft` query parameter --- src/Http/Controllers/API/ApiController.php | 5 +++++ tests/API/APITest.php | 25 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index edfc0d9a70..30c6fd568c 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -31,6 +31,11 @@ protected function abortIfUnpublished(Request $request, $item) return; } + // todo: we should also be checking that allowed_filters contains 'status' + if ($request->boolean('draft')) { + return; + } + throw_if($item->published() === false, new NotFoundHttpException); } diff --git a/tests/API/APITest.php b/tests/API/APITest.php index a0b68dc608..e5f9a95ddb 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -53,6 +53,31 @@ public static function entryNotFoundProvider() ]; } + /** @test */ + public function it_handles_unpublished_entries() + { + Facades\Config::set('statamic.api.resources.collections', [ + 'pages' => [ + 'allowed_filters' => ['status'], + ], + ]); + + Facades\Collection::make('pages')->save(); + Facades\Collection::make('articles')->save(); + + Facades\Entry::make()->collection('pages')->id('about')->slug('about')->published(false)->save(); + + $this->get('/api/collections/pages/entries/about')->assertJson([ + 'message' => 'Not found.', + ]); + + $this->get('/api/collections/pages/entries/about?draft=true')->assertJson([ + 'data' => [ + 'id' => 'about', + ], + ]); + } + public static function exampleFiltersProvider() { return [['status:is'], ['published:is'], ['title:is']]; From 98760c6a1a3fd6648d340925daca7676b9503af0 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 10:41:33 +0100 Subject: [PATCH 03/13] Ensure `allowed_filters` includes `status` filter --- src/Http/Controllers/API/ApiController.php | 3 +-- src/Http/Controllers/API/CollectionEntriesController.php | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index 30c6fd568c..a5984a848a 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -31,8 +31,7 @@ protected function abortIfUnpublished(Request $request, $item) return; } - // todo: we should also be checking that allowed_filters contains 'status' - if ($request->boolean('draft')) { + if (in_array('status', $this->allowedFilters()) && $request->boolean('draft')) { return; } diff --git a/src/Http/Controllers/API/CollectionEntriesController.php b/src/Http/Controllers/API/CollectionEntriesController.php index 3ebd339d7f..baaa1143f2 100644 --- a/src/Http/Controllers/API/CollectionEntriesController.php +++ b/src/Http/Controllers/API/CollectionEntriesController.php @@ -39,6 +39,7 @@ public function show(Request $request, $collection, $handle) $this->abortIfDisabled(); $entry = Entry::find($handle); + $this->collectionHandle = $entry?->collectionHandle(); $this->abortIfInvalid($entry, $collection); $this->abortIfUnpublished($request, $entry); From cd6b5d778ed402de06f272f8d0bc1c19f2ef8e61 Mon Sep 17 00:00:00 2001 From: duncanmcclean Date: Thu, 30 May 2024 09:54:45 +0000 Subject: [PATCH 04/13] Fix styling --- src/Http/Controllers/API/ApiController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index a5984a848a..7a80d4c4ce 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -22,7 +22,6 @@ class ApiController extends Controller /** * Abort if item is unpublished. * - * @param \Illuminate\Http\Request $request * @param mixed $item */ protected function abortIfUnpublished(Request $request, $item) From fd99cb11531d965ca9c3f4a31f18fb12fc3bc6af Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 11:09:12 +0100 Subject: [PATCH 05/13] simplify --- src/Http/Controllers/API/ApiController.php | 6 +++++- src/Http/Controllers/API/CollectionEntriesController.php | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index 7a80d4c4ce..54d5cdb96b 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -2,6 +2,7 @@ namespace Statamic\Http\Controllers\API; +use Facades\Statamic\API\FilterAuthorizer; use Facades\Statamic\API\ResourceAuthorizer; use Illuminate\Http\Request; use Statamic\Exceptions\ApiValidationException; @@ -30,7 +31,10 @@ protected function abortIfUnpublished(Request $request, $item) return; } - if (in_array('status', $this->allowedFilters()) && $request->boolean('draft')) { + if ( + $request->boolean('draft') + && in_array('status', FilterAuthorizer::allowedForSubResources('api', 'collections', $item->collectionHandle())) + ) { return; } diff --git a/src/Http/Controllers/API/CollectionEntriesController.php b/src/Http/Controllers/API/CollectionEntriesController.php index baaa1143f2..3ebd339d7f 100644 --- a/src/Http/Controllers/API/CollectionEntriesController.php +++ b/src/Http/Controllers/API/CollectionEntriesController.php @@ -39,7 +39,6 @@ public function show(Request $request, $collection, $handle) $this->abortIfDisabled(); $entry = Entry::find($handle); - $this->collectionHandle = $entry?->collectionHandle(); $this->abortIfInvalid($entry, $collection); $this->abortIfUnpublished($request, $entry); From e246b27ef62d37b70b8dafd2ba8ad0e2d3ee3e2a Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 11:09:51 +0100 Subject: [PATCH 06/13] Revert "simplify" This reverts commit fd99cb11531d965ca9c3f4a31f18fb12fc3bc6af. --- src/Http/Controllers/API/ApiController.php | 6 +----- src/Http/Controllers/API/CollectionEntriesController.php | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index 54d5cdb96b..7a80d4c4ce 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -2,7 +2,6 @@ namespace Statamic\Http\Controllers\API; -use Facades\Statamic\API\FilterAuthorizer; use Facades\Statamic\API\ResourceAuthorizer; use Illuminate\Http\Request; use Statamic\Exceptions\ApiValidationException; @@ -31,10 +30,7 @@ protected function abortIfUnpublished(Request $request, $item) return; } - if ( - $request->boolean('draft') - && in_array('status', FilterAuthorizer::allowedForSubResources('api', 'collections', $item->collectionHandle())) - ) { + if (in_array('status', $this->allowedFilters()) && $request->boolean('draft')) { return; } diff --git a/src/Http/Controllers/API/CollectionEntriesController.php b/src/Http/Controllers/API/CollectionEntriesController.php index 3ebd339d7f..baaa1143f2 100644 --- a/src/Http/Controllers/API/CollectionEntriesController.php +++ b/src/Http/Controllers/API/CollectionEntriesController.php @@ -39,6 +39,7 @@ public function show(Request $request, $collection, $handle) $this->abortIfDisabled(); $entry = Entry::find($handle); + $this->collectionHandle = $entry?->collectionHandle(); $this->abortIfInvalid($entry, $collection); $this->abortIfUnpublished($request, $entry); From e464a6eb90156f7a783c470cbdd5fab29b03781a Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 11:10:20 +0100 Subject: [PATCH 07/13] swap --- src/Http/Controllers/API/ApiController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index 7a80d4c4ce..a60d9355a4 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -30,7 +30,7 @@ protected function abortIfUnpublished(Request $request, $item) return; } - if (in_array('status', $this->allowedFilters()) && $request->boolean('draft')) { + if ($request->boolean('draft') && in_array('status', $this->allowedFilters())) { return; } From 5b5dfdbeb3512f980a26b15f1f0b7d37ef1ba6e8 Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 18:44:35 +0100 Subject: [PATCH 08/13] Make sure it's a "live preview token", not just any kind of token. --- src/Http/Controllers/API/ApiController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index a60d9355a4..d2f1524e64 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -26,7 +26,7 @@ class ApiController extends Controller */ protected function abortIfUnpublished(Request $request, $item) { - if ($request->statamicToken()) { + if ($request->isLivePreview()) { return; } From 4a02628e93306ff07c61d258e94abd0059be596f Mon Sep 17 00:00:00 2001 From: Duncan McClean Date: Thu, 30 May 2024 18:57:34 +0100 Subject: [PATCH 09/13] Add a test to ensure non-live-preview tokens don't allow drafts --- tests/API/APITest.php | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/API/APITest.php b/tests/API/APITest.php index e5f9a95ddb..b53bce550f 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -4,9 +4,12 @@ use Facades\Statamic\CP\LivePreview; use Facades\Statamic\Fields\BlueprintRepository; +use Illuminate\Support\Facades\Cache; use Statamic\Facades; use Statamic\Facades\Blueprint; +use Statamic\Facades\Token; use Statamic\Facades\User; +use Statamic\Tokens\Handlers\LivePreview as Handler; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -477,6 +480,24 @@ public function live_preview_token_bypasses_entry_status_check() ]); } + /** @test */ + public function non_live_preview_tokens_dont_bypasses_entry_status_check() + { + Facades\Config::set('statamic.api.resources.collections', true); + Facades\Collection::make('pages')->save(); + $entry = tap(Facades\Entry::make()->collection('pages')->id('dance')->published(false)->set('title', 'Dance')->slug('dance'))->save(); + + $this->get('/api/collections/pages/entries/dance')->assertJson([ + 'message' => 'Not found.', + ]); + + Token::make('test-token', FakeTokenHandler::class)->save(); + + $this->get('/api/collections/pages/entries/dance?token=test-token')->assertJson([ + 'message' => 'Not found.', + ]); + } + /** @test */ public function it_replaces_terms_using_live_preview_token() { @@ -599,3 +620,11 @@ private function assertEndpointNotFound($endpoint) ->assertJson(['message' => 'Not found.']); } } + +class FakeTokenHandler +{ + public function handle(\Statamic\Contracts\Tokens\Token $token, \Illuminate\Http\Request $request, \Closure $next) + { + return $next($token); + } +} From 372d71082a3af66bfef123a806f7d06fea3d0902 Mon Sep 17 00:00:00 2001 From: duncanmcclean Date: Thu, 30 May 2024 17:59:20 +0000 Subject: [PATCH 10/13] Fix styling --- tests/API/APITest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/API/APITest.php b/tests/API/APITest.php index b53bce550f..d0d5d0d520 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -4,12 +4,10 @@ use Facades\Statamic\CP\LivePreview; use Facades\Statamic\Fields\BlueprintRepository; -use Illuminate\Support\Facades\Cache; use Statamic\Facades; use Statamic\Facades\Blueprint; use Statamic\Facades\Token; use Statamic\Facades\User; -use Statamic\Tokens\Handlers\LivePreview as Handler; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; From 683bb04132d9879858af4e63eaac199e084bcabf Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Jun 2024 14:51:32 -0400 Subject: [PATCH 11/13] call it drafts instead of draft --- src/Http/Controllers/API/ApiController.php | 2 +- tests/API/APITest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index d2f1524e64..7d43fedfc2 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -30,7 +30,7 @@ protected function abortIfUnpublished(Request $request, $item) return; } - if ($request->boolean('draft') && in_array('status', $this->allowedFilters())) { + if ($request->boolean('drafts') && in_array('status', $this->allowedFilters())) { return; } diff --git a/tests/API/APITest.php b/tests/API/APITest.php index 8c406728cd..9276f0ac47 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -71,7 +71,7 @@ public function it_handles_unpublished_entries() 'message' => 'Not found.', ]); - $this->get('/api/collections/pages/entries/about?draft=true')->assertJson([ + $this->get('/api/collections/pages/entries/about?drafts=true')->assertJson([ 'data' => [ 'id' => 'about', ], From b4bd7d6baf1506715c7b6801671b032b20b0e21b Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Jun 2024 15:23:39 -0400 Subject: [PATCH 12/13] revert the draft stuff, leaving just the live preview changes --- src/Http/Controllers/API/ApiController.php | 9 ++----- .../API/CollectionEntriesController.php | 6 ++--- tests/API/APITest.php | 25 ------------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/src/Http/Controllers/API/ApiController.php b/src/Http/Controllers/API/ApiController.php index 7d43fedfc2..4ab286fdb1 100644 --- a/src/Http/Controllers/API/ApiController.php +++ b/src/Http/Controllers/API/ApiController.php @@ -3,7 +3,6 @@ namespace Statamic\Http\Controllers\API; use Facades\Statamic\API\ResourceAuthorizer; -use Illuminate\Http\Request; use Statamic\Exceptions\ApiValidationException; use Statamic\Exceptions\NotFoundHttpException; use Statamic\Facades\Site; @@ -24,13 +23,9 @@ class ApiController extends Controller * * @param mixed $item */ - protected function abortIfUnpublished(Request $request, $item) + protected function abortIfUnpublished($item) { - if ($request->isLivePreview()) { - return; - } - - if ($request->boolean('drafts') && in_array('status', $this->allowedFilters())) { + if (request()->isLivePreview()) { return; } diff --git a/src/Http/Controllers/API/CollectionEntriesController.php b/src/Http/Controllers/API/CollectionEntriesController.php index baaa1143f2..892d823c96 100644 --- a/src/Http/Controllers/API/CollectionEntriesController.php +++ b/src/Http/Controllers/API/CollectionEntriesController.php @@ -3,7 +3,6 @@ namespace Statamic\Http\Controllers\API; use Facades\Statamic\API\FilterAuthorizer; -use Illuminate\Http\Request; use Statamic\Exceptions\NotFoundHttpException; use Statamic\Facades\Entry; use Statamic\Http\Resources\API\EntryResource; @@ -34,15 +33,14 @@ public function index($collection) ); } - public function show(Request $request, $collection, $handle) + public function show($collection, $handle) { $this->abortIfDisabled(); $entry = Entry::find($handle); - $this->collectionHandle = $entry?->collectionHandle(); $this->abortIfInvalid($entry, $collection); - $this->abortIfUnpublished($request, $entry); + $this->abortIfUnpublished($entry); return app(EntryResource::class)::make($entry); } diff --git a/tests/API/APITest.php b/tests/API/APITest.php index 9276f0ac47..24a34647ff 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -53,31 +53,6 @@ public static function entryNotFoundProvider() ]; } - #[Test] - public function it_handles_unpublished_entries() - { - Facades\Config::set('statamic.api.resources.collections', [ - 'pages' => [ - 'allowed_filters' => ['status'], - ], - ]); - - Facades\Collection::make('pages')->save(); - Facades\Collection::make('articles')->save(); - - Facades\Entry::make()->collection('pages')->id('about')->slug('about')->published(false)->save(); - - $this->get('/api/collections/pages/entries/about')->assertJson([ - 'message' => 'Not found.', - ]); - - $this->get('/api/collections/pages/entries/about?drafts=true')->assertJson([ - 'data' => [ - 'id' => 'about', - ], - ]); - } - public static function exampleFiltersProvider() { return [['status:is'], ['published:is'], ['title:is']]; From 7659e212026597f2c7909c49fa69d2bf02206c64 Mon Sep 17 00:00:00 2001 From: Jason Varga Date: Tue, 25 Jun 2024 15:23:48 -0400 Subject: [PATCH 13/13] grammar --- tests/API/APITest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/API/APITest.php b/tests/API/APITest.php index 24a34647ff..15b7c3f68e 100644 --- a/tests/API/APITest.php +++ b/tests/API/APITest.php @@ -447,7 +447,7 @@ public function live_preview_token_bypasses_entry_status_check() } #[Test] - public function non_live_preview_tokens_dont_bypasses_entry_status_check() + public function non_live_preview_tokens_doesnt_bypass_entry_status_check() { Facades\Config::set('statamic.api.resources.collections', true); Facades\Collection::make('pages')->save();