diff --git a/app/ExternalService.php b/app/ExternalService.php index 8f50d76..aaf14c9 100644 --- a/app/ExternalService.php +++ b/app/ExternalService.php @@ -30,12 +30,17 @@ class ExternalService extends Model public function hasOAuthStandardOptions() { - // This list must be mirrored in external_services.js + // This logic must be mirrored in external_services.js return in_array($this->service_type, [ - self::DROPBOX, self::FACEBOOK, self::GOOGLE, self::TWITTER ]); } + + public function isDropbox() + { + // This logic must be mirrored in external_services.js + return $this->service_type == self::DROPBOX; + } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/ServiceController.php b/app/Http/Controllers/Admin/ServiceController.php index c71f779..691aed4 100644 --- a/app/Http/Controllers/Admin/ServiceController.php +++ b/app/Http/Controllers/Admin/ServiceController.php @@ -7,6 +7,8 @@ use App\Facade\Theme; use App\Facade\UserConfig; use App\Http\Controllers\Controller; use App\Http\Requests\StoreServiceRequest; +use App\Services\DropboxService; +use App\Storage; use Illuminate\Http\Request; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\View; @@ -36,6 +38,53 @@ class ServiceController extends Controller $this->fieldsToEncrypt = ['app_id', 'app_secret']; } + public function authoriseDropbox(Request $request) + { + $this->authorizeAccessToAdminPanel('admin:manage-storage'); + + if (!$request->has('state') && !$request->has('code')) + { + // TODO flash an error + return redirect('storages.index'); + } + + try + { + $storageID = decrypt($request->get('state')); + + $storage = Storage::where('id', intval($storageID))->first(); + if (is_null($storage)) + { + // TODO flash an error + return redirect('storages.index'); + } + + if (is_null($storage->externalService)) + { + // TODO flash an error + return redirect('storages.index'); + } + + switch ($storage->externalService->service_type) + { + case ExternalService::DROPBOX: + $dropbox = new DropboxService(); + $dropbox->handleAuthenticationResponse($request, $storage); + // TODO flash a success message + return redirect(route('storage.index')); + + default: + // TODO flash an error + return redirect('storages.index'); + } + } + catch (\Exception $ex) + { + // TODO flash an error + return redirect('storages.index'); + } + } + /** * Show the form for creating a new resource. * @@ -46,6 +95,7 @@ class ServiceController extends Controller $this->authorizeAccessToAdminPanel('admin:manage-services'); return Theme::render('admin.create_service', [ + 'callbackUrls' => $this->callbackList(), 'service' => new ExternalService(), 'serviceTypes' => $this->serviceTypeList() ]); @@ -136,6 +186,7 @@ class ServiceController extends Controller } return Theme::render('admin.edit_service', [ + 'callbackUrls' => $this->callbackList(), 'service' => $service, 'serviceTypes' => $this->serviceTypeList() ]); @@ -222,6 +273,15 @@ class ServiceController extends Controller return redirect(route('services.index')); } + private function callbackList() + { + $dropboxService = new DropboxService(); + + return [ + ExternalService::DROPBOX => $dropboxService->callbackUrl() + ]; + } + private function isServiceInUse(ExternalService $service) { // TODO check if the service is in use anywhere else and prevent it being deleted if so diff --git a/app/Http/Controllers/Admin/StorageController.php b/app/Http/Controllers/Admin/StorageController.php index 3a0b7dc..9c41c28 100644 --- a/app/Http/Controllers/Admin/StorageController.php +++ b/app/Http/Controllers/Admin/StorageController.php @@ -28,7 +28,7 @@ class StorageController extends Controller $this->encryptedFields = ['password', 'access_key', 'secret_key', 'access_token']; } - public function authoriseService($id) + public function authoriseService(Request $request, $id) { $this->authorizeAccessToAdminPanel('admin:manage-storage'); @@ -38,47 +38,35 @@ class StorageController extends Controller App::abort(404); } - if (is_null($storage->externalService)) + $externalServiceType = $this->getExternalServiceType($storage); + + if (is_null($externalServiceType)) { - App::abort(400, 'Storage does not support an external service'); + $request->session()->flash('error', trans('admin.storage_no_external_service_support')); + return redirect(route('storages.index')); } - switch ($storage->externalService->service_type) + $serviceTypeName = trans(sprintf('services.%s', $externalServiceType)); + $viewData = [ + 'service' => $storage->externalService, + 'serviceName' => $serviceTypeName, + 'storage' => $storage + ]; + + switch ($externalServiceType) { case ExternalService::DROPBOX: $dropbox = new DropboxService(); - return redirect($dropbox->authoriseUrl($storage)); + $viewData['authoriseUrl'] = $dropbox->authoriseUrl($storage); + $viewData['callbackUrl'] = $dropbox->callbackUrl(); + break; default: - App::abort(400, 'External service does not support authorisation'); - } - } - - public function completeServiceAuthorisation(Request $request, $id) - { - $this->authorizeAccessToAdminPanel('admin:manage-storage'); - - $storage = Storage::where('id', intval($id))->first(); - if (is_null($storage)) - { - App::abort(404); + $request->session()->flash('error', trans('admin.storage_external_service_no_authorisation', ['service_name' => $serviceTypeName])); + return redirect(route('storages.index')); } - if (is_null($storage->externalService)) - { - App::abort(400, 'Storage does not support an external service'); - } - - switch ($storage->externalService->service_type) - { - case ExternalService::DROPBOX: - $dropbox = new DropboxService(); - $dropbox->handleAuthenticationResponse($request, $storage); - return redirect(route('storage.index')); - - default: - App::abort(400, 'External service does not support authorisation'); - } + return Theme::render('admin.authorise_external_service', $viewData); } /** @@ -175,6 +163,17 @@ class StorageController extends Controller $this->unsetIsDefaultFromOthers($storage); } + $externalServiceType = $this->getExternalServiceType($storage); + + if (!is_null($externalServiceType)) + { + switch ($externalServiceType) + { + case ExternalService::DROPBOX: + return redirect(route('storage.authoriseService', ['storage' => $storage->id])); + } + } + return redirect(route('storage.index')); } @@ -351,6 +350,16 @@ class StorageController extends Controller return redirect(route('storage.index')); } + private function getExternalServiceType(Storage $storage) + { + if (!is_null($storage->externalService)) + { + return $storage->externalService->service_type; + } + + return null; + } + private function setIsDefaultForFirstStorage() { $count = Storage::where('is_default', true)->count(); diff --git a/app/Services/DropboxService.php b/app/Services/DropboxService.php index 2a1a885..8b22db3 100644 --- a/app/Services/DropboxService.php +++ b/app/Services/DropboxService.php @@ -27,16 +27,22 @@ class DropboxService public function authoriseUrl(Storage $storage) { $service = $storage->externalService; - $redirectUrl = route('storage.completeServiceAuthorisation', ['storage' => $storage->id]); + $redirectUrl = $this->callbackUrl(); return sprintf( - '%s?client_id=%s&response_type=code&redirect_uri=%s', + '%s?client_id=%s&response_type=code&redirect_uri=%s&state=%s', $this->config['authorise_url'], urlencode(decrypt($service->app_id)), - urlencode($redirectUrl) + urlencode($redirectUrl), + urlencode(encrypt($storage->id)) ); } + public function callbackUrl() + { + return route('services.authoriseDropbox'); + } + public function downloadFile($pathOnStorage) { $dropboxArgs = ['path' => $pathOnStorage]; @@ -100,7 +106,7 @@ class DropboxService { $service = $storage->externalService; $credentials = sprintf('%s:%s', decrypt($service->app_id), decrypt($service->app_secret)); - $redirectUrl = route('storage.completeServiceAuthorisation', ['storage' => $storage->id]); + $redirectUrl = $this->callbackUrl(); $httpHeaders = [ 'Accept: application/json', diff --git a/resources/js/external_services.js b/resources/js/external_services.js index 5bbf5aa..e6baca0 100644 --- a/resources/js/external_services.js +++ b/resources/js/external_services.js @@ -7,11 +7,15 @@ function ExternalServiceViewModel() this.computed = { hasOAuthStandardOptions() { - // This list must be mirrored in App\ExternalService - return this.service_type === 'dropbox' || - this.service_type === 'facebook' || + // This logic must be mirrored in App\ExternalService + return this.service_type === 'facebook' || this.service_type === 'google' || this.service_type === 'twitter'; + }, + isDropbox() + { + // This logic must be mirrored in App\ExternalService + return this.service_type === 'dropbox'; } } } \ No newline at end of file diff --git a/resources/lang/en/admin.php b/resources/lang/en/admin.php index 6cb3b37..b6b9cc6 100644 --- a/resources/lang/en/admin.php +++ b/resources/lang/en/admin.php @@ -63,6 +63,10 @@ return [ 'approve_comment_confirm' => 'Are you sure you want to approve this comment by ":author_name"?', 'approve_comments' => 'Approve :number comments', 'approve_comments_confirm' => 'Are you sure you want to approve these :number comments?', + 'authorise_service_authorise' => 'Authorise', + 'authorise_service_authorise_intro' => 'Click the Authorise button to login to :name.', + 'authorise_service_intro' => 'Blue Twilight needs authorisation to access your :name account.', + 'authorise_service_title' => 'Authorise access to :name', 'bulk_comments_approved' => ':number comment was approved successfully.|:number comments were approved successfully.', 'bulk_comments_deleted' => ':number comment was deleted successfully.|:number comments were deleted successfully.', 'bulk_photos_changed' => ':number photo was updated successfully.|:number photos were updated successfully.', @@ -269,6 +273,7 @@ return [ 'visible_action' => 'Only those visible' ], 'select_none_action' => 'Clear selection', + 'service_callback_intro' => 'Please ensure you add the below URL to your :name app as a "trusted", "callback" or "redirect" URL.', 'service_deletion_failed' => 'An error occurred while removing the :name service: :error_message', 'service_deletion_successful' => 'The :name service was removed successfully.', 'services_title' => 'External services', @@ -344,6 +349,8 @@ return [ 'storage_authorise_external_service_refresh_authentication' => 'Refresh authentication', 'storage_authorise_external_service_required' => 'Authorisation required', 'storage_backblaze_access_key_id_help' => 'To use your account\'s master key, enter your account ID here.', + 'storage_external_service_no_authorisation' => ':service_name does not support authentication.', + 'storage_no_external_service_support' => 'This storage driver does not support an external service.', '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', diff --git a/resources/lang/en/navigation.php b/resources/lang/en/navigation.php index d385045..a6a14da 100644 --- a/resources/lang/en/navigation.php +++ b/resources/lang/en/navigation.php @@ -6,6 +6,7 @@ return [ 'admin' => 'Admin', 'albums' => 'Albums', 'approve_comment' => 'Approve comment', + 'authorise_service' => 'Authorise service', 'comments' => 'Comments', 'create_album' => 'Create album', 'create_group' => 'Create group', diff --git a/resources/views/themes/base/admin/authorise_external_service.blade.php b/resources/views/themes/base/admin/authorise_external_service.blade.php new file mode 100644 index 0000000..081f06a --- /dev/null +++ b/resources/views/themes/base/admin/authorise_external_service.blade.php @@ -0,0 +1,27 @@ +@extends(Theme::viewName('layout')) +@section('title', trans('admin.authorise_service_title', ['name' => $serviceName])) + +@section('breadcrumb') + + + + +@endsection + +@section('content') +
+
+
+
+
@yield('title')
+
+

@lang('admin.authorise_service_intro', ['name' => $serviceName])

+ +

@lang('admin.authorise_service_authorise_intro', ['name' => $serviceName])

+

@lang('admin.authorise_service_authorise')

+
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/themes/base/admin/create_service.blade.php b/resources/views/themes/base/admin/create_service.blade.php index 1a3a7cf..9d221a2 100644 --- a/resources/views/themes/base/admin/create_service.blade.php +++ b/resources/views/themes/base/admin/create_service.blade.php @@ -50,6 +50,9 @@
@include(Theme::viewName('partials.admin_services_oauth_options'))
+
+ @include(Theme::viewName('partials.admin_services_dropbox_options')) +
@lang('forms.cancel_action') diff --git a/resources/views/themes/base/admin/edit_service.blade.php b/resources/views/themes/base/admin/edit_service.blade.php index adaefe7..edc3210 100644 --- a/resources/views/themes/base/admin/edit_service.blade.php +++ b/resources/views/themes/base/admin/edit_service.blade.php @@ -50,6 +50,8 @@ @if ($service->hasOAuthStandardOptions()) @include(Theme::viewName('partials.admin_services_oauth_options')) + @elseif ($service->isDropbox()) + @include(Theme::viewName('partials.admin_services_dropbox_options')) @endif
diff --git a/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php b/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php new file mode 100644 index 0000000..b48c00f --- /dev/null +++ b/resources/views/themes/base/partials/admin_services_dropbox_options.blade.php @@ -0,0 +1,31 @@ +
+
+
+ + + + @if ($errors->has('app_id')) +
+ {{ $errors->first('app_id') }} +
+ @endif +
+
+
+
+ + + + @if ($errors->has('app_secret')) +
+ {{ $errors->first('app_secret') }} +
+ @endif +
+
+
+ +
+

@lang('admin.service_callback_intro', ['name' => trans(sprintf('services.%s', \App\ExternalService::DROPBOX))])

+

{{ $callbackUrls[\App\ExternalService::DROPBOX] }}

+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 7529b8f..d3d9574 100644 --- a/routes/web.php +++ b/routes/web.php @@ -56,7 +56,6 @@ Route::group(['prefix' => 'admin'], function () { // Storage management Route::get('storage/{storage}/authorise-service', 'Admin\StorageController@authoriseService')->name('storage.authoriseService'); - Route::get('storage/{storage}/complete-service-authorisation', 'Admin\StorageController@completeServiceAuthorisation')->name('storage.completeServiceAuthorisation'); Route::get('storage/{storage}/delete', 'Admin\StorageController@delete')->name('storage.delete'); Route::resource('storage', 'Admin\StorageController'); @@ -83,6 +82,7 @@ Route::group(['prefix' => 'admin'], function () { Route::resource('comments', 'Admin\PhotoCommentController'); // Services management + Route::get('services/authorise-dropbox', 'Admin\ServiceController@authoriseDropbox')->name('services.authoriseDropbox'); Route::get('services/{service}/delete', 'Admin\ServiceController@delete')->name('services.delete'); Route::resource('services', 'Admin\ServiceController'); });