diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index e272c3f..cc91216 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,7 +11,7 @@ jobs: runs-on: 'ubuntu-latest' strategy: matrix: - php-versions: [ '8.0', '8.1', '8.2' ] + php-versions: [ '8.2', '8.3', '8.4', '8.5', ] steps: - uses: actions/checkout@v2 diff --git a/composer.json b/composer.json index 9b4aa82..61bd124 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "league/mime-type-detection": "^1.11" }, "require-dev": { - "phpunit/phpunit": "@stable", + "phpunit/phpunit": "^10.0", "league/flysystem-adapter-test-utilities": "^3", "league/flysystem-memory": "^3.0", "league/flysystem-path-prefixing": "^3.3", diff --git a/readme.md b/readme.md index b98e4af..3a57ecf 100644 --- a/readme.md +++ b/readme.md @@ -28,6 +28,9 @@ $adapter = new BunnyCDNAdapter( ) ); +// for temporary URL support, define a signing key +$adapter->setTokenAuthKey('token-auth-signing-key'); + $filesystem = new Filesystem($adapter); ``` @@ -45,6 +48,10 @@ $adapter = new BunnyCDNAdapter( ), 'https://testing.b-cdn.net/' # Pull Zone URL ); + +// for temporary URL support, define a signing key +$adapter->setTokenAuthKey('token-auth-signing-key'); + $filesystem = new Filesystem($adapter); ``` @@ -52,7 +59,8 @@ _Note: You can also use your own domain name if it's configured in the pull zone Once you add your pull zone, you can use the `->getUrl($path)`, or in Laravel, the `->url($path)` command to get the fully qualified public URL of your BunnyCDN assets. -## Usage in Laravel 9 +## Usage in Laravel 9 & up + To add BunnyCDN adapter as a custom storage adapter in Laravel 9, install using the `v3` composer installer. ```bash @@ -78,6 +86,9 @@ Next, install the adapter to your `AppServiceProvider` to give Laravel's FileSys ), $config['pull_zone'] ); + + // for temporary URL support, define a signing key + $adapter->setTokenAuthKey('token-auth-signing-key'); return new FilesystemAdapter( new Filesystem($adapter, $config), @@ -98,6 +109,7 @@ Finally, add the `bunnycdn` driver into your `config/filesystems.php` configurat 'storage_zone' => env('BUNNYCDN_STORAGE_ZONE'), 'pull_zone' => env('BUNNYCDN_PULL_ZONE'), 'api_key' => env('BUNNYCDN_API_KEY'), + 'token_auth_key' => env('BUNNYCDN_TOKEN_AUTH_KEY', ''), // optional if you'd like signed URLs 'region' => env('BUNNYCDN_REGION', \PlatformCommunity\Flysystem\BunnyCDN\BunnyCDNRegion::DEFAULT) ], @@ -111,6 +123,7 @@ BUNNYCDN_STORAGE_ZONE=testing_storage_zone BUNNYCDN_PULL_ZONE=https://testing.b-cdn.net BUNNYCDN_API_KEY="api-key" # BUNNYCDN_REGION=uk +#BUNNYCDN_TOKEN_AUTH_KEY="your-token-auth-key" (optional, under CDN > Security > Token Authentication) ``` After that, you can use the `bunnycdn` disk in Laravel 9. diff --git a/src/BunnyCDNAdapter.php b/src/BunnyCDNAdapter.php index f67ec84..a1c81b6 100644 --- a/src/BunnyCDNAdapter.php +++ b/src/BunnyCDNAdapter.php @@ -40,16 +40,27 @@ class BunnyCDNAdapter implements FilesystemAdapter, PublicUrlGenerator, Checksum { use CalculateChecksumFromStream; + private string $token_auth_key = ''; + public function __construct( private BunnyCDNClient $client, private string $pullzone_url = '', - private string $token_auth_key = '' ) { if (\func_num_args() > 2 && (string) \func_get_arg(2) !== '') { throw new \RuntimeException('PrefixPath is no longer supported directly. Use PathPrefixedAdapter instead: https://flysystem.thephpleague.com/docs/adapter/path-prefixing/'); } } + /** + * Set the token auth key for generating temporaryUrls. + */ + public function setTokenAuthKey(string $tokenAuthKey): BunnyCDNAdapter + { + $this->token_auth_key = $tokenAuthKey; + + return $this; + } + /** * @param $source * @param $destination @@ -563,7 +574,7 @@ public function publicUrl(string $path, Config $config): string public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string { if ($this->token_auth_key === '') { - throw new UnableToGenerateTemporaryUrl('In order to generate temporary URLs for a BunnyCDN object, you must pass the "token_auth_key" parameter to the BunnyCDNAdapter.', $path); + throw new UnableToGenerateTemporaryUrl('In order to generate temporary URLs for a BunnyCDN object, you must call the `setTokenAuthKey` method on the BunnyCDNAdapter.', $path); } // convert our expiration to a unix timestamp diff --git a/tests/FlysystemAdapterTest.php b/tests/FlysystemAdapterTest.php index 95c52bd..c939913 100644 --- a/tests/FlysystemAdapterTest.php +++ b/tests/FlysystemAdapterTest.php @@ -13,7 +13,6 @@ use League\Flysystem\FilesystemException; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToDeleteFile; -use League\Flysystem\UnableToGenerateTemporaryUrl; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToProvideChecksum; use League\Flysystem\UnableToRetrieveMetadata; @@ -92,7 +91,10 @@ private static function bunnyCDNClient(): BunnyCDNClient public static function createFilesystemAdapter(): FilesystemAdapter { - return new BunnyCDNAdapter(self::bunnyCDNClient(), static::$publicUrl); + $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), static::$publicUrl); + $adapter->setTokenAuthKey('test-token-auth-key'); + + return $adapter; } /** @@ -103,25 +105,6 @@ public function setting_visibility(): void $this->markTestSkipped('No visibility support is provided for BunnyCDN'); } - public function generating_a_temporary_url(): void - { - $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), '', 'test-key'); - - $expiresAt = new \DateTimeImmutable('+1 hour'); - $url = $adapter->temporaryUrl('path.txt', $expiresAt, new Config()); - - $this->assertStringContainsString('path.txt?token=', $url); - $this->assertStringContainsString('&expires=', $url); - } - - public function test_temporary_url_throws_exception_if_not_configured(): void - { - $this->expectException(UnableToGenerateTemporaryUrl::class); - $this->expectExceptionMessage('In order to generate temporary URLs for a BunnyCDN object, you must pass the "token_auth_key" parameter to the BunnyCDNAdapter.'); - - $this->adapter()->temporaryUrl('path.txt', new \DateTimeImmutable('+1 hour'), new Config()); - } - /** * @test */ @@ -320,6 +303,21 @@ public function test_without_pullzone_url_error_thrown_accessing_url(): void $myAdapter->publicUrl('/path.txt', new Config()); } + /** + * @test + */ + public function generating_a_temporary_url(): void + { + $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), ''); + $adapter->setTokenAuthKey('test-key'); + + $expiresAt = new \DateTimeImmutable('+1 hour'); + $url = $adapter->temporaryUrl('path.txt', $expiresAt, new Config()); + + $this->assertStringContainsString('path.txt?token=', $url); + $this->assertStringContainsString('&expires=', $url); + } + /** * @test */ @@ -412,6 +410,7 @@ public function test_checksum_throws_error_with_non_existing_file_on_default_alg public function test_checksum_throws_error_with_empty_checksum_from_client(): void { $client = $this->createMock(BunnyCDNClient::class); + $client->expects(self::exactly(1))->method('list')->willReturnCallback( function () { ['file' => $file, 'dir' => $dir] = Util::splitPathIntoDirectoryAndFile('file.txt'); diff --git a/tests/PrefixTest.php b/tests/PrefixTest.php index 8d2a600..417a205 100644 --- a/tests/PrefixTest.php +++ b/tests/PrefixTest.php @@ -38,7 +38,10 @@ private static function bunnyCDNClient(): BunnyCDNClient private static function bunnyCDNAdapter(): BunnyCDNAdapter { - return new BunnyCDNAdapter(self::bunnyCDNClient(), 'https://example.org.local/assets/'); + $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), 'https://example.org.local/assets/'); + $adapter->setTokenAuthKey('test-token-auth-key'); + + return $adapter; } public static function createFilesystemAdapter(): FilesystemAdapter @@ -65,18 +68,6 @@ public function setting_visibility(): void $this->markTestSkipped('No visibility support is provided for BunnyCDN'); } - public function generating_a_temporary_url(): void - { - $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), '', 'test-key'); - $prefixAdapter = new PathPrefixedAdapter($adapter, self::PREFIX_PATH); - - $expiresAt = new \DateTimeImmutable('+1 hour'); - $url = $prefixAdapter->temporaryUrl('path.txt', $expiresAt, new Config()); - - $this->assertStringContainsString(self::PREFIX_PATH.'/path.txt?token=', $url); - $this->assertStringContainsString('&expires=', $url); - } - /** * Overwritten (usually because of visibility) */ @@ -108,6 +99,23 @@ public function overwriting_a_file(): void }); } + /** + * @test + */ + public function generating_a_temporary_url(): void + { + $adapter = new BunnyCDNAdapter(self::bunnyCDNClient(), 'https://example.org.local/assets/'); + $adapter->setTokenAuthKey('test-token-auth-key'); + + $prefixAdapter = new PathPrefixedAdapter($adapter, self::PREFIX_PATH); + + $expiresAt = new \DateTimeImmutable('+1 hour'); + $url = $prefixAdapter->temporaryUrl('path.txt', $expiresAt, new Config()); + + $this->assertStringContainsString(self::PREFIX_PATH.'/path.txt?token=', $url); + $this->assertStringContainsString('&expires=', $url); + } + /** * @test */ diff --git a/tests/TemporaryUrlTest.php b/tests/TemporaryUrlTest.php new file mode 100644 index 0000000..4c25cb0 --- /dev/null +++ b/tests/TemporaryUrlTest.php @@ -0,0 +1,37 @@ +expectException(UnableToGenerateTemporaryUrl::class); + $this->expectExceptionMessage('you must call the `setTokenAuthKey`'); + + $client = new BunnyCDNClient('test', 'test'); + $adapter = new BunnyCDNAdapter($client, 'pz-key'); + + $expiresAt = new \DateTimeImmutable('+1 hour'); + $adapter->temporaryUrl('testing.text', $expiresAt, new Config()); + } + + public function test_it_can_generate_signing_key() + { + $client = new BunnyCDNClient('test', 'test'); + $adapter = new BunnyCDNAdapter($client, 'pz-key'); + $adapter->setTokenAuthKey('test-auth-key'); + + $expiresAt = new \DateTimeImmutable('+1 hour'); + $url = $adapter->temporaryUrl('testing.txt', $expiresAt, new Config()); + + $this->assertStringContainsString('testing.txt?token=', $url); + $this->assertStringContainsString('expires='.$expiresAt->getTimestamp(), $url); + } +}