config = config('services.dropbox'); } public function authoriseUrl(Storage $storage) { $service = $storage->externalService; $redirectUrl = $this->callbackUrl(); return sprintf( '%s?client_id=%s&response_type=code&redirect_uri=%s&state=%s', $this->config['authorise_url'], urlencode(decrypt($service->app_id)), urlencode($redirectUrl), urlencode(encrypt($storage->id)) ); } public function callbackUrl() { return route('services.authoriseDropbox'); } public function deleteFile($pathOnStorage) { $dropboxData = ['path' => $pathOnStorage]; $deleteResult = $this->sendRequest( $this->config['delete_url'], 'POST', $dropboxData, [ 'http_headers' => [ 'Content-Type: application/json' ], 'post_body_is_json' => true ] ); Log::debug('DropboxService - response to deleteFile.', ['response' => $deleteResult, 'path' => $pathOnStorage]); } public function downloadFile($pathOnStorage) { $dropboxArgs = ['path' => $pathOnStorage]; return $this->sendRequest( $this->config['download_url'], 'POST', null, [ 'http_headers' => [ sprintf('Dropbox-API-Arg: %s', json_encode($dropboxArgs)), 'Content-Type: application/octet-stream' ], 'post_body_is_json' => false, 'response_body_is_json' => false ] ); } public function handleAuthenticationResponse(Request $request, Storage $storage) { $authorisationCode = $request->query('code'); $storage->access_token = encrypt($this->convertAuthorisationCodeToToken($authorisationCode, $storage)); $storage->save(); return true; } /** * @param string $accessToken */ public function setAccessToken(string $accessToken) { $this->accessToken = $accessToken; } public function uploadFile($pathToFileToUpload, $pathOnStorage) { $dropboxArgs = [ 'path' => $pathOnStorage, 'mode' => 'overwrite', 'mute' => true ]; $shouldRetry = true; while ($shouldRetry) { try { $uploadResult = $this->sendRequest( $this->config['upload_url'], 'POST', file_get_contents($pathToFileToUpload), [ 'http_headers' => [ sprintf('Dropbox-API-Arg: %s', json_encode($dropboxArgs)), 'Content-Type: application/octet-stream' ], 'post_body_is_json' => false ] ); $shouldRetry = false; Log::debug('DropboxService - response to uploadFile.', ['response' => $uploadResult, 'path' => $pathOnStorage]); } catch (DropboxRetryException $dre) { // Retry - leave shouldRetry as true Log::debug('DropboxService - Dropbox reported a lock/rate limit and requested to retry'); sleep(2); } catch (\Exception $ex) { $shouldRetry = false; Log::debug('DropboxService - exception in uploadFile.', ['exception' => $ex->getMessage()]); } } } private function convertAuthorisationCodeToToken($authorisationCode, Storage $storage) { $service = $storage->externalService; $credentials = sprintf('%s:%s', decrypt($service->app_id), decrypt($service->app_secret)); $redirectUrl = $this->callbackUrl(); $httpHeaders = [ 'Accept: application/json', sprintf('Authorization: Basic %s', base64_encode($credentials)) ]; $ch = curl_init($this->config['token_url']); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, [ 'code' => $authorisationCode, 'grant_type' => 'authorization_code', 'redirect_uri' => $redirectUrl ]); $response = json_decode(curl_exec($ch)); if (is_null($response) || $response === false) { throw new \Exception('Unable to read the response from Dropbox'); } else if (isset($response->error_description)) { throw new \Exception(sprintf('Error from Dropbox: %s', $response->error_description)); } return $response->access_token; } private function getBasicHttpClient($url, $method = 'GET', array $httpHeaders = []) { $httpHeaders = array_merge( [ 'Accept: application/json', sprintf('Authorization: Bearer %s', $this->accessToken) ], $httpHeaders ); $ch = curl_init($url); curl_setopt($ch, CURLOPT_HTTPHEADER, $httpHeaders); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); switch (strtoupper($method)) { case 'GET': curl_setopt($ch, CURLOPT_HTTPGET, true); break; case 'POST': curl_setopt($ch, CURLOPT_POST, true); break; } return $ch; } private function sendRequest($url, $method = 'GET', $postData = null, array $postOptions = []) { $postOptions = array_merge( [ 'http_headers' => [], 'post_body_is_json' => true, 'response_body_is_json' => true ], $postOptions ); $httpHeaders = $postOptions['http_headers']; $ch = $this->getBasicHttpClient($url, $method, $httpHeaders); Log::info(sprintf('DropboxService - %s: %s', strtoupper($method), $url)); Log::debug('DropboxService - HTTP headers:', $httpHeaders); if (!is_null($postData)) { if ($postOptions['post_body_is_json']) { // Only log a post body if we have one and it's in JSON format (i.e. not a file upload) Log::debug('DropboxService - Body: ', $postData); $postData = json_encode($postData); } curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); } $result = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); Log::info(sprintf('DropboxService - Received HTTP code %d', $httpCode)); // Only log a result if we have one and it's in JSON format (i.e. not a file download) if (!is_null($result) && $result !== false && $postOptions['response_body_is_json']) { Log::debug($result); } try { if ($httpCode != 200 && $httpCode != 304) { if ($httpCode == 429) { throw new DropboxRetryException($httpCode, new \Exception(sprintf('Exception from Dropbox: %s', $result))); } throw new \Exception(sprintf('Exception from Dropbox: %s', $result)); } return $postOptions['response_body_is_json'] ? json_decode($result) : $result; } finally { curl_close($ch); } } }