diff --git a/app/AlbumSources/BackblazeB2Source.php b/app/AlbumSources/BackblazeB2Source.php index acde3e4..c6c21d7 100644 --- a/app/AlbumSources/BackblazeB2Source.php +++ b/app/AlbumSources/BackblazeB2Source.php @@ -2,7 +2,6 @@ namespace App\AlbumSources; -use App\Album; use App\Photo; use App\Services\BackblazeB2Service; use App\Storage; @@ -10,15 +9,26 @@ use Guzzle\Http\EntityBody; class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource { + const BUCKET_TYPE_AUTO = 0; + const BUCKET_TYPE_PRIVATE = 1; + const BUCKET_TYPE_PUBLIC = 2; + /** * @var BackblazeB2Service */ private $backblaze; - public function __construct() - { - $this->backblaze = new BackblazeB2Service(); - } + /** + * Type of bucket which determines what type of URLs to generate to images. + * @var integer + */ + private $bucketType; + + /** + * Token used to download files from a private bucket. + * @var string + */ + private $downloadToken; /** * Deletes an entire album's media contents. @@ -68,7 +78,31 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource */ public function getUrlToPhoto(Photo $photo, $thumbnail = null) { - // TODO: Implement getUrlToPhoto() method. + $client = $this->getClient(); + $storagePathToFile = $this->getPathToPhoto($photo, $thumbnail); + + /* + * From https://www.backblaze.com/b2/docs/b2_download_file_by_name.html: + * The base URL to use comes from the b2_authorize_account call, and looks something like + * https://f345.backblazeb2.com. The "f" in the URL stands for "file", and the number is the cluster + * number containing your account. To this base, you add "file/", your bucket name, a "/", and then the + * name of the file. The file name may itself include more "/" characters. + */ + $fileDownloadUrl = sprintf('%s/file/%s/%s', $client->getDownloadUrl(), $this->configuration->container_name, $storagePathToFile); + + switch ($this->bucketType) + { + case self::BUCKET_TYPE_PRIVATE: + if (is_null($this->downloadToken)) + { + $this->downloadToken = $client->getDownloadAuthToken(); + } + + return sprintf('%s?Authorization=%s', $fileDownloadUrl, $this->downloadToken); + + case self::BUCKET_TYPE_PUBLIC: + return $fileDownloadUrl; + } } /** @@ -88,14 +122,38 @@ class BackblazeB2Source extends AlbumSourceBase implements IAlbumSource public function setConfiguration(Storage $configuration) { parent::setConfiguration($configuration); - - $this->backblaze->setCredentials(decrypt($configuration->access_key), decrypt($configuration->secret_key)); } private function getClient() { - $this->backblaze->authorizeAccount(); - $this->backblaze->setBucketName($this->configuration->container_name); + if (is_null($this->backblaze)) + { + $this->backblaze = new BackblazeB2Service(); + $this->backblaze->setCredentials(decrypt($this->configuration->access_key), decrypt($this->configuration->secret_key)); + $this->backblaze->authorizeAccount(); + $this->backblaze->setBucketName($this->configuration->container_name); + + if (intval($this->configuration->b2_bucket_type) == self::BUCKET_TYPE_AUTO) + { + /* Auto-detect the type of bucket in use on B2 */ + + switch ($this->backblaze->getBucketType()) + { + case 'allPrivate': + $this->configuration->b2_bucket_type = self::BUCKET_TYPE_PRIVATE; + break; + + case 'allPublic': + $this->configuration->b2_bucket_type = self::BUCKET_TYPE_PUBLIC; + break; + } + + $this->configuration->save(); + } + + // Set the bucket type + $this->bucketType = $this->configuration->b2_bucket_type; + } return $this->backblaze; } diff --git a/app/Http/Controllers/Admin/StorageController.php b/app/Http/Controllers/Admin/StorageController.php index 7093643..6a1c775 100644 --- a/app/Http/Controllers/Admin/StorageController.php +++ b/app/Http/Controllers/Admin/StorageController.php @@ -88,7 +88,8 @@ class StorageController extends Controller 'container_name', 'cdn_url', 'access_key', - 'secret_key' + 'secret_key', + 'b2_bucket_type' ])); $storage->is_active = true; $storage->is_default = (strtolower($request->get('is_default')) == 'on'); @@ -217,7 +218,8 @@ class StorageController extends Controller 'container_name', 'cdn_url', 'access_key', - 'secret_key' + 'secret_key', + 'b2_bucket_type' ])); $storage->is_active = (strtolower($request->get('is_active')) == 'on'); $storage->is_default = (strtolower($request->get('is_default')) == 'on'); diff --git a/app/Services/BackblazeB2Service.php b/app/Services/BackblazeB2Service.php index eb98438..de0671d 100644 --- a/app/Services/BackblazeB2Service.php +++ b/app/Services/BackblazeB2Service.php @@ -10,14 +10,46 @@ class BackblazeB2Service */ private $accountApiUrl; + /** + * ID of the account in Backblaze B2. + * @var string + */ private $accountID; + /** + * The base URL for public access to the account's files. + * @var string + */ + private $downloadUrl; + + /** + * Authorisation header for authenticating to the API. + * @var string + */ private $authHeader; + /** + * Authorisation token for accessing the API post-authentication. + * @var string + */ private $authToken; + /** + * ID of the bucket. + * @var string + */ private $bucketId; + /** + * Type of the bucket. + * @var integer + */ + private $bucketType; + + /** + * Configuration related to the Backblaze B2 service. + * @var \Illuminate\Config\Repository|mixed + */ private $config; public function __construct() @@ -39,12 +71,41 @@ class BackblazeB2Service $this->authToken = $result->authorizationToken; $this->accountApiUrl = $result->apiUrl; $this->accountID = $result->accountId; + $this->downloadUrl = $result->downloadUrl; } } + public function getBucketType() + { + return $this->bucketType; + } + + public function getDownloadAuthToken() + { + $result = $this->sendRequest( + sprintf('%s/b2api/v2/b2_get_download_authorization', $this->accountApiUrl), + 'POST', + [ + 'bucketId' => $this->bucketId, + 'validDurationInSeconds' => intval($this->config['download_token_lifetime']), + 'fileNamePrefix' => '' + ] + ); + + return $result->authorizationToken; + } + + public function getDownloadUrl() + { + return $this->downloadUrl; + } + public function setBucketName($bucketName) { - $this->bucketId = $this->getBucketIdFromName($bucketName); + $bucketDetails = $this->getBucketDetailsFromName($bucketName); + + $this->bucketId = $bucketDetails->bucketId; + $this->bucketType = $bucketDetails->bucketType; } public function setCredentials($applicationKeyID, $applicationKey) @@ -78,12 +139,17 @@ class BackblazeB2Service curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContents); $result = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - var_dump($result); - exit(); + if ($httpCode != 200 && $httpCode != 304) + { + throw new \Exception(sprintf('Exception from Backblaze B2: %s', $result)); + } + + curl_close($ch); } - private function getBucketIdFromName($bucketName) + private function getBucketDetailsFromName($bucketName) { $result = $this->sendRequest( sprintf('%s/b2api/v2/b2_list_buckets', $this->accountApiUrl), @@ -96,7 +162,7 @@ class BackblazeB2Service if (isset($result->buckets) && is_array($result->buckets) && count($result->buckets) >= 1) { - return $result->buckets[0]->bucketId; + return $result->buckets[0]; } throw new \Exception(sprintf('The bucket \'%s\' was not found or your API key does not have access.', $bucketName)); diff --git a/app/Storage.php b/app/Storage.php index cd7cd42..660178c 100644 --- a/app/Storage.php +++ b/app/Storage.php @@ -30,7 +30,8 @@ class Storage extends Model 'container_name', 'cdn_url', 'access_key', - 'secret_key' + 'secret_key', + 'b2_bucket_type' ]; public function albums() diff --git a/config/services.php b/config/services.php index c658a92..e7ace08 100644 --- a/config/services.php +++ b/config/services.php @@ -15,7 +15,8 @@ return [ */ 'backblaze_b2' => [ - 'auth_url' => 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account' + 'auth_url' => 'https://api.backblazeb2.com/b2api/v2/b2_authorize_account', + 'download_token_lifetime' => 300 ], 'gitea' => [ diff --git a/database/migrations/2019_09_09_205137_add_backblaze_storage_columns.php b/database/migrations/2019_09_09_205137_add_backblaze_storage_columns.php new file mode 100644 index 0000000..e6d3e74 --- /dev/null +++ b/database/migrations/2019_09_09_205137_add_backblaze_storage_columns.php @@ -0,0 +1,34 @@ +tinyInteger('b2_bucket_type')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('storages', function (Blueprint $table) + { + $table->dropColumn('b2_bucket_type'); + }); + } +} diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 14674f1..0e1cc96 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -101,6 +101,12 @@ return [ 'storage_application_key_id_label' => 'Application key ID:', 'storage_application_key_label' => 'Application key:', 'storage_auth_url_label' => 'Authentication URL:', + 'storage_b2_bucket_type' => [ + 'autodetect' => 'Auto-detect', + 'label' => 'Bucket type:', + 'private' => 'Private', + 'public' => 'Public' + ], 'storage_bucket_name_label' => 'Bucket name:', 'storage_cdn_url_label' => 'Public CDN URL (if supported and enabled):', 'storage_container_name_label' => 'Container name:', diff --git a/resources/views/themes/base/partials/admin_storages_backblaze_b2_options.blade.php b/resources/views/themes/base/partials/admin_storages_backblaze_b2_options.blade.php index c903ed7..4450e21 100644 --- a/resources/views/themes/base/partials/admin_storages_backblaze_b2_options.blade.php +++ b/resources/views/themes/base/partials/admin_storages_backblaze_b2_options.blade.php @@ -39,4 +39,20 @@ @endif +
+
+ + + + @if ($errors->has('b2_bucket_type')) +
+ {{ $errors->first('b2_bucket_type') }} +
+ @endif +
+
\ No newline at end of file