From cef90a8e22b49ea61c50f00afb29f10a89aa661f Mon Sep 17 00:00:00 2001 From: Lukas Bestle Date: Thu, 5 Dec 2024 22:07:58 +0100 Subject: [PATCH] Support preview tokens for custom blueprint config --- src/Content/Version.php | 60 +++++++++++++++++++++++++++------- tests/Cms/Pages/PageTest.php | 27 ++++++++-------- tests/Content/VersionTest.php | 61 +++++++++++++++++++---------------- 3 files changed, 96 insertions(+), 52 deletions(-) diff --git a/src/Content/Version.php b/src/Content/Version.php index 0ef61b751b..c0da2e3412 100644 --- a/src/Content/Version.php +++ b/src/Content/Version.php @@ -11,6 +11,7 @@ use Kirby\Exception\LogicException; use Kirby\Exception\NotFoundException; use Kirby\Form\Form; +use Kirby\Http\Query; use Kirby\Http\Uri; use Kirby\Toolkit\Str; @@ -431,8 +432,13 @@ protected function previewTokenFromUrl(string $url): string|null return null; } + // get rid of all modifiers after the path + $uri = new Uri($url); + $uri->fragment = null; + $uri->query = new Query([]); + $data = [ - 'uri' => Str::after($url, $localPrefix), + 'uri' => Str::after($uri->toString(), $localPrefix), 'versionId' => $this->id->value() ]; @@ -590,24 +596,56 @@ public function url(): string|null $url = $this->model->blueprint()->preview(); + // preview was disabled if ($url === false) { return null; } - $url = match ($url) { - true, null => $this->model->url(), - default => $url - }; - - $uri = new Uri($url); - + // we only need to add a token for draft and changes previews if ( - ($this->model instanceof Page && $this->model->isDraft() === true) || - $this->id->is('changes') === true + ($this->model instanceof Site || $this->model->isDraft() === false) && + $this->id->is('changes') === false ) { - $uri->query->_token = $this->previewToken(); + return match (true) { + is_string($url) => $url, + default => $this->model->url() + }; + } + + // check if the URL was customized + if (is_string($url) === true) { + return $this->urlFromOption($url); + } + + // it wasn't, use the safer/more reliable model-based preview token + return $this->urlParams($this->model->url(), $this->previewToken()); + } + + /** + * Returns the preview URL based on an arbitrary URL from + * the blueprint option + */ + protected function urlFromOption(string $url): string + { + // try to determine a token for a local preview + // (we cannot determine the token for external previews) + if ($token = $this->previewTokenFromUrl($url)) { + return $this->urlParams($url, $token); } + // fall back to the URL as defined in the blueprint + return $url; + } + + /** + * Assembles the preview URL with the added `_token` and `_version` + * query params, no matter if the base URL already contains query params + */ + protected function urlParams(string $baseUrl, string $token): string + { + $uri = new Uri($baseUrl); + $uri->query->_token = $token; + if ($this->id->is('changes') === true) { $uri->query->_version = 'changes'; } diff --git a/tests/Cms/Pages/PageTest.php b/tests/Cms/Pages/PageTest.php index f463c51ff7..6e2714b538 100644 --- a/tests/Cms/Pages/PageTest.php +++ b/tests/Cms/Pages/PageTest.php @@ -607,18 +607,18 @@ public function testPreviewUrlUnauthenticated() public static function previewUrlProvider(): array { return [ - [null, '/test', false], - [null, '/test?{token}', true], - [true, '/test', false], - [true, '/test?{token}', true], - ['/something/different', '/something/different', false], - ['/something/different', '/something/different?{token}', true], - ['{{ site.url }}#{{ page.slug }}', '/#test', false], - ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', true], - ['{{ page.url }}?preview=true', '/test?preview=true&{token}', true], - [false, null, false], - [false, null, true], - [null, null, false, false], + [null, '/test', null, false], + [null, '/test?{token}', 'test', true], + [true, '/test', null, false], + [true, '/test?{token}', 'test', true], + ['/something/different', '/something/different', null, false], + ['/something/different', '/something/different?{token}', 'something\/different', true], + ['{{ site.url }}#{{ page.slug }}', '/#test', null, false], + ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', '', true], + ['{{ page.url }}?preview=true', '/test?preview=true&{token}', 'test', true], + [false, null, null, false], + [false, null, null, true], + [null, null, null, false, false], ]; } @@ -628,6 +628,7 @@ public static function previewUrlProvider(): array public function testCustomPreviewUrl( $input, $expected, + $expectedUri, bool $draft, bool $authenticated = true ): void { @@ -677,7 +678,7 @@ public function testCustomPreviewUrl( ]); if ($draft === true && $expected !== null) { - $expectedToken = substr(hash_hmac('sha1', '{"uri":"' . $page->uri() . '","versionId":"latest"}', $page->kirby()->root('content')), 0, 10); + $expectedToken = substr(hash_hmac('sha1', '{"uri":"' . $expectedUri . '","versionId":"latest"}', $page->kirby()->root('content')), 0, 10); $expected = str_replace( '{token}', '_token=' . $expectedToken, diff --git a/tests/Content/VersionTest.php b/tests/Content/VersionTest.php index e317851a25..61bd679d7e 100644 --- a/tests/Content/VersionTest.php +++ b/tests/Content/VersionTest.php @@ -1511,34 +1511,38 @@ public static function pageUrlProvider(): array { return [ // latest version - [null, '/test', false, 'latest'], - [null, '/test?{token}', true, 'latest'], - [true, '/test', false, 'latest'], - [true, '/test?{token}', true, 'latest'], - ['/something/different', '/something/different', false, 'latest'], - ['/something/different', '/something/different?{token}', true, 'latest'], - ['{{ site.url }}#{{ page.slug }}', '/#test', false, 'latest'], - ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', true, 'latest'], - ['{{ page.url }}?preview=true', '/test?preview=true', false, 'latest'], - ['{{ page.url }}?preview=true', '/test?preview=true&{token}', true, 'latest'], - [false, null, false, 'latest'], - [false, null, true, 'latest'], - [null, null, false, 'latest', false], + [null, '/test', null, false, 'latest'], + [null, '/test?{token}', 'test', true, 'latest'], + [true, '/test', null, false, 'latest'], + [true, '/test?{token}', 'test', true, 'latest'], + ['https://test.com', 'https://test.com', null, false, 'latest'], + ['https://test.com', 'https://test.com', null, true, 'latest'], + ['/something/different', '/something/different', 'something\/different', false, 'latest'], + ['/something/different', '/something/different?{token}', 'something\/different', true, 'latest'], + ['{{ site.url }}#{{ page.slug }}', '/#test', null, false, 'latest'], + ['{{ site.url }}#{{ page.slug }}', '/?{token}#test', '', true, 'latest'], + ['{{ page.url }}?preview=true', '/test?preview=true', null, false, 'latest'], + ['{{ page.url }}?preview=true', '/test?preview=true&{token}', 'test', true, 'latest'], + [false, null, null, false, 'latest'], + [false, null, null, true, 'latest'], + [null, null, null, false, 'latest', false], // changes version - [null, '/test?{token}&_version=changes', false, 'changes'], - [null, '/test?{token}&_version=changes', true, 'changes'], - [true, '/test?{token}&_version=changes', false, 'changes'], - [true, '/test?{token}&_version=changes', true, 'changes'], - ['/something/different', '/something/different?{token}&_version=changes', false, 'changes'], - ['/something/different', '/something/different?{token}&_version=changes', true, 'changes'], - ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', false, 'changes'], - ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', true, 'changes'], - ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', false, 'changes'], - ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', true, 'changes'], - [false, null, false, 'changes'], - [false, null, true, 'changes'], - [null, null, false, 'changes', false], + [null, '/test?{token}&_version=changes', 'test', false, 'changes'], + [null, '/test?{token}&_version=changes', 'test', true, 'changes'], + [true, '/test?{token}&_version=changes', 'test', false, 'changes'], + [true, '/test?{token}&_version=changes', 'test', true, 'changes'], + ['https://test.com', 'https://test.com', null, false, 'changes'], + ['https://test.com', 'https://test.com', null, true, 'changes'], + ['/something/different', '/something/different?{token}&_version=changes', 'something\/different', false, 'changes'], + ['/something/different', '/something/different?{token}&_version=changes', 'something\/different', true, 'changes'], + ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', '', false, 'changes'], + ['{{ site.url }}#{{ page.slug }}', '/?{token}&_version=changes#test', '', true, 'changes'], + ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', 'test', false, 'changes'], + ['{{ page.url }}?preview=true', '/test?preview=true&{token}&_version=changes', 'test', true, 'changes'], + [false, null, null, false, 'changes'], + [false, null, null, true, 'changes'], + [null, null, null, false, 'changes', false], ]; } @@ -1549,6 +1553,7 @@ public static function pageUrlProvider(): array public function testUrlPageCustom( $input, $expected, + $expectedUri, bool $draft, string $versionId, bool $authenticated = true @@ -1597,7 +1602,7 @@ public function testUrlPageCustom( $expectedToken = substr( hash_hmac( 'sha1', - '{"uri":"' . $page->uri() . '","versionId":"' . $versionId . '"}', + '{"uri":"' . $expectedUri . '","versionId":"' . $versionId . '"}', $page->kirby()->root('content') ), 0, @@ -1663,7 +1668,7 @@ public static function siteUrlProvider(): array // changes version [null, '/?{token}&_version=changes', 'changes'], - ['https://test.com', 'https://test.com?{token}&_version=changes', 'changes'], + ['https://test.com', 'https://test.com', 'changes'], ['{{ site.url }}#test', '/?{token}&_version=changes#test', 'changes'], [false, null, 'changes'], [null, null, 'changes', false],