diff --git a/app/AlbumSources/AlbumSourceBase.php b/app/AlbumSources/AlbumSourceBase.php index 374e17f..a5ac38a 100644 --- a/app/AlbumSources/AlbumSourceBase.php +++ b/app/AlbumSources/AlbumSourceBase.php @@ -42,6 +42,11 @@ abstract class AlbumSourceBase return self::$albumSourceCache[$fullClassName]; } + public function getConfiguration() + { + return $this->configuration; + } + public function setAlbum(Album $album) { $this->album = $album; diff --git a/app/AlbumSources/AmazonS3Source.php b/app/AlbumSources/AmazonS3Source.php index 6694f5b..9ac3164 100644 --- a/app/AlbumSources/AmazonS3Source.php +++ b/app/AlbumSources/AmazonS3Source.php @@ -130,7 +130,19 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ */ public function getUrlToPhoto(Photo $photo, $thumbnail = null) { - return $this->getClient()->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail)); + $client = $this->getClient(); + + if ($this->configuration->s3_signed_urls) + { + $cmd = $client->getCommand('GetObject', [ + 'Bucket' => $this->configuration->container_name, + 'Key' => $this->getPathToPhoto($photo, $thumbnail) + ]); + + return $client->createPresignedRequest($cmd, '+5 minutes')->getUri(); + } + + return $client->getObjectUrl($this->configuration->container_name, $this->getPathToPhoto($photo, $thumbnail)); } /** @@ -144,7 +156,11 @@ class AmazonS3Source extends AlbumSourceBase implements IAlbumSource, IAnalysisQ { $photoPath = $this->getPathToPhoto($photo, $thumbnail); - $this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), 'public-read'); + $uploadAcl = $this->configuration->s3_signed_urls + ? 'private' + : 'public-read'; + + $this->getClient()->upload($this->configuration->container_name, $photoPath, fopen($tempFilename, 'r+'), $uploadAcl); } /** diff --git a/app/AlbumSources/IAlbumSource.php b/app/AlbumSources/IAlbumSource.php index 4e98635..aecce32 100644 --- a/app/AlbumSources/IAlbumSource.php +++ b/app/AlbumSources/IAlbumSource.php @@ -6,7 +6,6 @@ use App\Album; use App\Photo; use App\Storage; use Guzzle\Http\EntityBody; -use Symfony\Component\HttpFoundation\File\File; interface IAlbumSource { @@ -32,6 +31,12 @@ interface IAlbumSource */ function fetchPhotoContent(Photo $photo, $thumbnail = null); + /** + * Gets the configuration of the source. + * @return mixed + */ + function getConfiguration(); + /** * Gets the name of this album source. * @return string diff --git a/app/Http/Controllers/Admin/StorageController.php b/app/Http/Controllers/Admin/StorageController.php index 6a1c775..f4c53c0 100644 --- a/app/Http/Controllers/Admin/StorageController.php +++ b/app/Http/Controllers/Admin/StorageController.php @@ -55,12 +55,14 @@ class StorageController extends Controller $this->authorizeAccessToAdminPanel('admin:manage-storage'); $filesystemDefaultLocation = sprintf('%s/storage/app/albums', dirname(dirname(dirname(dirname(__DIR__))))); + $storage = new Storage(); + $storage->s3_signed_urls = true; return Theme::render('admin.create_storage', [ 'album_sources' => UserConfig::albumSources(), 'filesystem_default_location' => $filesystemDefaultLocation, 'info' => $request->session()->get('info'), - 'storage' => new Storage() + 'storage' => $storage ]); } @@ -94,6 +96,7 @@ class StorageController extends Controller $storage->is_active = true; $storage->is_default = (strtolower($request->get('is_default')) == 'on'); $storage->is_internal = false; + $storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on'); if ($storage->source != 'LocalFilesystemSource' && isset($storage->location)) { @@ -219,10 +222,12 @@ class StorageController extends Controller 'cdn_url', 'access_key', 'secret_key', - 'b2_bucket_type' + 'b2_bucket_type', + 's3_signed_urls' ])); $storage->is_active = (strtolower($request->get('is_active')) == 'on'); $storage->is_default = (strtolower($request->get('is_default')) == 'on'); + $storage->s3_signed_urls = (strtolower($request->get('s3_signed_urls')) == 'on'); if ($storage->is_default && !$storage->is_active) { diff --git a/app/Photo.php b/app/Photo.php index a4fef8b..3a3e892 100644 --- a/app/Photo.php +++ b/app/Photo.php @@ -2,6 +2,7 @@ namespace App; +use App\AlbumSources\IAlbumSource; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -110,9 +111,14 @@ class Photo extends Model public function thumbnailUrl($thumbnailName = null, $cacheBust = true) { - $url = $this->album->getAlbumSource()->getUrlToPhoto($this, $thumbnailName); + /** @var IAlbumSource $source */ + $source = $this->album->getAlbumSource(); + $sourceConfiguration = $source->getConfiguration(); - if ($cacheBust) + $url = $source->getUrlToPhoto($this, $thumbnailName); + + // Cache busting doesn't work with S3 signed URLs + if ($cacheBust && !$sourceConfiguration->s3_signed_urls) { // Append the timestamp of the last update to avoid browser caching $theDate = is_null($this->updated_at) ? $this->created_at : $this->updated_at; diff --git a/database/migrations/2020_04_18_212642_add_s3_signed_urls_column_to_storages_table.php b/database/migrations/2020_04_18_212642_add_s3_signed_urls_column_to_storages_table.php new file mode 100644 index 0000000..af6aff9 --- /dev/null +++ b/database/migrations/2020_04_18_212642_add_s3_signed_urls_column_to_storages_table.php @@ -0,0 +1,32 @@ +boolean('s3_signed_urls'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('storages', function (Blueprint $table) { + $table->dropColumn('s3_signed_urls'); + }); + } +} diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index b844d06..1a8561a 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -323,7 +323,10 @@ return [ 'photos' => 'photo|photos', 'users' => 'user|users', ], + 'storage_auth_url_label_help' => 'Leave blank to authenticate with Amazon S3. For an S3-compatible provider, enter your provider\'s authentication URL here.', 'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.', + 'storage_s3_signed_urls_help' => 'When enabled, Blue Twilight will upload your photos with a private ACL and will use signed URLs to display the photos to your visitors.', + 'storage_s3_signed_urls_tooltip' => 'This location is set to use private images with signed URLs.', 'storage_title' => 'Storage Locations', 'sysinfo_panel' => 'System information', 'sysinfo_widget' => [ diff --git a/resources/lang/en/forms.php b/resources/lang/en/forms.php index 0e1cc96..14777d8 100644 --- a/resources/lang/en/forms.php +++ b/resources/lang/en/forms.php @@ -113,6 +113,7 @@ return [ 'storage_driver_label' => 'Storage driver:', 'storage_endpoint_url_label' => 'Endpoint URL (leave blank if using Amazon):', 'storage_location_label' => 'Physical location:', + 'storage_s3_signed_urls' => 'Upload files privately and use signed URLs', 'storage_secret_key_label' => 'Secret key:', 'storage_service_name_label' => 'Service name:', 'storage_service_region_label' => 'Region:', diff --git a/resources/views/themes/base/admin/edit_storage.blade.php b/resources/views/themes/base/admin/edit_storage.blade.php index c7d4705..56594cc 100644 --- a/resources/views/themes/base/admin/edit_storage.blade.php +++ b/resources/views/themes/base/admin/edit_storage.blade.php @@ -61,13 +61,13 @@ @include(Theme::viewName('partials.admin_storages_rackspace_options')) @endif -