Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE support Temporary URLs #50

Open
tinect opened this issue Nov 22, 2022 · 2 comments
Open

FEATURE support Temporary URLs #50

tinect opened this issue Nov 22, 2022 · 2 comments

Comments

@tinect
Copy link
Contributor

tinect commented Nov 22, 2022

Temporary URLs could be supported by BunnyCDN-Token-Authentications

I have no need for it currently, nor do I see a real case that makes it necessary at the moment. But I think, when flysystem has this feature, there could be really cool needs. :-) Having a look, I thought it could be a feature to implement.

@black-forest-digital
Copy link

I agree - my case would be profile pictures. They should be available within my web application for the specific user, but not totally public.

@bnussbau
Copy link

bnussbau commented Oct 25, 2024

I needed quick support for this in my Laravel application and resolved it by extending the Adapter and using the code sample from Bunny (https://github.com/BunnyWay/BunnyCDN.TokenAuthentication/blob/master/php/url_signing.php)

In config/filesystems.php

        'bunnycdn' => [
            'driver' => 'bunnycdn',
            'storage_zone' => env('BUNNYCDN_STORAGE_ZONE'),
            'api_key' => env('BUNNYCDN_API_KEY'),
            'region' => env('BUNNYCDN_REGION'),
            'pullzone_url' => env('BUNNYCDN_PULLZONE_URL'),
            'security_key' => env('BUNNYCDN_SECURITY_KEY'),
        ],

In app/providers/AppServiceProvider.php

public function boot(): void
{
        $this->addBunnyStorageAdapter();
}

public function addBunnyStorageAdapter(): void
{
      Storage::extend('bunnycdn', function (Application $app, array $config) {
            $adapter = new BunnyCdnWithTemporaryUrlAdapter(
                new BunnyCDNClient(
                    $config['storage_zone'],
                    $config['api_key'],
                    $config['region']
                ),
                $config['pullzone_url']
            );

            return new FilesystemAdapter(
                new Filesystem($adapter, $config),
                $adapter,
                $config
            );
        });
}
<?php

namespace App\Support\Flysystem;

use DateTimeInterface;
use League\Flysystem\ChecksumProvider;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToGenerateTemporaryUrl;
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
use PlatformCommunity\Flysystem\BunnyCDN\BunnyCDNAdapter;
use Throwable;

class BunnyCdnWithTemporaryUrlAdapter extends BunnyCDNAdapter implements FilesystemAdapter, PublicUrlGenerator, ChecksumProvider, TemporaryUrlGenerator
{
    // Laravel checks if this method exists to determine if temporary URLs can be generated
    public function getTemporaryUrl(string $path, DateTimeInterface $expiresAt, $config): string
    {
        return $this->temporaryUrl($path, $expiresAt, $config);
    }

    public function temporaryUrl(string $path, DateTimeInterface $expiresAt, $config): string
    {
        if (config('filesystems.disks.bunnycdn.pullzone_url') === '') {
            throw UnableToGenerateTemporaryUrl::dueToError($path, 'In order to get a temporary URL for a BunnyCDN object, you must pass the "pullzone_url" parameter to the BunnyCDNAdapter.');
        }

        if (config('filesystems.disks.bunnycdn.security_key') === '') {
            throw UnableToGenerateTemporaryUrl::dueToError($path, 'In order to get a temporary URL for a BunnyCDN object, you must pass the "security_key" parameter (Url Token Authentication Key) to the BunnyCDNAdapter.');
        }

        try {
            $unsignedUrl = rtrim(config('filesystems.disks.bunnycdn.pullzone_url'), '/') . '/' . ltrim($path, '/');
            return $this->sign_bcdn_url($unsignedUrl, config('filesystems.disks.bunnycdn.security_key'), $expiresAt->getTimestamp() - time());
        } catch (Throwable $exception) {
            throw UnableToGenerateTemporaryUrl::dueToError($path, $exception);
        }
    }

    // BunnyCDN php sample code
    // see https://github.com/BunnyWay/BunnyCDN.TokenAuthentication/blob/master/php/url_signing.php
    function sign_bcdn_url($url, $securityKey, $expiration_time = 3600, $user_ip = NULL, $is_directory_token = false, $path_allowed = NULL, $countries_allowed = NULL, $countries_blocked = NULL, $referers_allowed = NULL): string
    {
        if (!is_null($countries_allowed)) {
            $url .= (parse_url($url, PHP_URL_QUERY) == "") ? "?" : "&";
            $url .= "token_countries={$countries_allowed}";
        }
        if (!is_null($countries_blocked)) {
            $url .= (parse_url($url, PHP_URL_QUERY) == "") ? "?" : "&";
            $url .= "token_countries_blocked={$countries_blocked}";
        }
        if (!is_null($referers_allowed)) {
            $url .= (parse_url($url, PHP_URL_QUERY) == "") ? "?" : "&";
            $url .= "token_referer={$referers_allowed}";
        }

        $url_scheme = parse_url($url, PHP_URL_SCHEME);
        $url_host = parse_url($url, PHP_URL_HOST);
        $url_path = parse_url($url, PHP_URL_PATH);
        $url_query = parse_url($url, PHP_URL_QUERY);


        $parameters = array();
        parse_str($url_query, $parameters);

        // Check if the path is specified and ovewrite the default
        $signature_path = $url_path;

        if (!is_null($path_allowed)) {
            $signature_path = $path_allowed;
            $parameters["token_path"] = $signature_path;
        }

        // Expiration time
        $expires = time() + $expiration_time;

        // Construct the parameter data
        ksort($parameters); // Sort alphabetically, very important
        $parameter_data = "";
        $parameter_data_url = "";
        if (sizeof($parameters) > 0) {
            foreach ($parameters as $key => $value) {
                if (strlen($parameter_data) > 0)
                    $parameter_data .= "&";

                $parameter_data_url .= "&";

                $parameter_data .= "{$key}=" . $value;
                $parameter_data_url .= "{$key}=" . urlencode($value); // URL encode everything but slashes for the URL data
            }
        }

        // Generate the toke
        $hashableBase = $securityKey . $signature_path . $expires;

        // If using IP validation
        if (!is_null($user_ip)) {
            $hashableBase .= $user_ip;
        }

        $hashableBase .= $parameter_data;

        // Generate the token
        $token = hash('sha256', $hashableBase, true);
        $token = base64_encode($token);
        $token = strtr($token, '+/', '-_');
        $token = str_replace('=', '', $token);

        if ($is_directory_token) {
            return "{$url_scheme}://{$url_host}/bcdn_token={$token}&expires={$expires}{$parameter_data_url}{$url_path}";
        } else {
            return "{$url_scheme}://{$url_host}{$url_path}?token={$token}{$parameter_data_url}&expires={$expires}";
        }
    }
}

In .env set

BUNNYCDN_PULLZONE_URL=
BUNNYCDN_SECURITY_KEY=

Security Key can be acquired from the Bunny Dashboard located at CDN > Security > Token Authentication > Url Token Authentication Key

Usage

$url = Storage::disk('bunnycdn')->temporaryUrl('test.txt', now()->addSeconds(10));
$this->info("test.txt temporary URL valid for 10 seconds: {$url}");

I still would like to see this officially supported, if there is a chance to get a PR reviewed and merged quickly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants