diff --git a/Gemfile b/Gemfile index d9a81ad9ce..76a00f1d04 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,7 @@ gem 'dbf', '2.0.6' gem 'faraday', '0.9.0' gem 'retriable', '1.4.1' # google-api-client needs this gem 'google-api-client', '0.7.0' -gem 'dropbox-sdk', '1.6.3' +gem 'dropbox_api', '0.1.6' gem 'instagram', '1.1.6' gem 'gibbon', '1.1.4' diff --git a/Gemfile.lock b/Gemfile.lock index 1dba67f952..f1705a8ff0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -106,8 +106,9 @@ GEM thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.1.3) docile (1.1.5) - dropbox-sdk (1.6.3) - json + dropbox_api (0.1.6) + faraday (~> 0.9, ~> 0.8) + oauth2 (~> 1.1) ejs (1.1.1) em-pg-client (0.2.1) eventmachine (>= 0.12.10) @@ -192,6 +193,12 @@ GEM oauth (0.4.5) oauth-plugin (0.4.0.pre4) oauth (>= 0.4.4) + oauth2 (1.3.1) + faraday (>= 0.8, < 0.12) + jwt (~> 1.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) pg (0.15.0) poltergeist (1.0.3) capybara (~> 1.1) @@ -373,7 +380,7 @@ DEPENDENCIES db-query-matchers (= 0.4.0) dbf (= 2.0.6) delorean - dropbox-sdk (= 1.6.3) + dropbox_api (= 0.1.6) ejs (~> 1.1.1) em-pg-client (= 0.2.1) eventmachine (= 1.0.4) diff --git a/lib/dropbox_api/endpoints/auth/token/revoke.rb b/lib/dropbox_api/endpoints/auth/token/revoke.rb new file mode 100644 index 0000000000..b082a7c02d --- /dev/null +++ b/lib/dropbox_api/endpoints/auth/token/revoke.rb @@ -0,0 +1,30 @@ +require 'dropbox_api' + +# These classes add toking revoking features to dropbox_api gem. They should be eventually sent there. +module DropboxApi::Metadata + class Revoke < Base + def initialize(_response_data) + # response_data is empty + end + end +end + +module DropboxApi::Endpoints + module Auth + module Token + class Revoke < DropboxApi::Endpoints::Rpc + Method = :post + Path = "/2/auth/token/revoke".freeze + ResultType = DropboxApi::Metadata::Revoke + ErrorType = DropboxApi::Errors::BasicError + + # Revoke token + # + # @return [Revoke] Empty response + add_endpoint :revoke do + perform_request nil + end + end + end + end +end diff --git a/services/datasources/lib/datasources/url/dropbox.rb b/services/datasources/lib/datasources/url/dropbox.rb index 3295afd22e..a93b221b2d 100644 --- a/services/datasources/lib/datasources/url/dropbox.rb +++ b/services/datasources/lib/datasources/url/dropbox.rb @@ -1,7 +1,8 @@ # encoding: utf-8 -require 'dropbox_sdk' +require 'dropbox_api' require_relative '../base_oauth' +require_relative '../../../../../lib/dropbox_api/endpoints/auth/token/revoke' module CartoDB module Datasources @@ -62,46 +63,24 @@ module CartoDB end # Return the url to be displayed or sent the user to to authenticate and get authorization code - # @param use_callback_flow : bool + # Older implementations had a use_callback_flow parameter that became deprecated. Not implemented. # @throws AuthError - def get_auth_url(use_callback_flow=true) - if use_callback_flow - @auth_flow = DropboxOAuth2Flow.new(@app_key, @app_secret, @callback_url, {}, :csrf_token) - else - @auth_flow = DropboxOAuth2FlowNoRedirect.new(@app_key, @app_secret) - end - service_name = service_name_for_user(DATASOURCE_NAME, @user) - @auth_flow.start(CALLBACK_STATE_DATA_PLACEHOLDER.sub('user', @user.username).sub('service', service_name)) - rescue DropboxError, ArgumentError => ex + def get_auth_url + authenticator.authorize_url redirect_uri: @callback_url, state: state + rescue => ex raise AuthError.new("get_auth_url(#{use_callback_flow}): #{ex.message}", DATASOURCE_NAME) end - # Validate authorization code and store token - # @param auth_code : string - # @return string : Access token - # @throws AuthError - def validate_auth_code(auth_code) - @auth_flow = DropboxOAuth2FlowNoRedirect.new(@app_key, @app_secret) - data = @auth_flow.finish(auth_code) - @access_token = data[0] # Only keep the access token - @auth_flow = nil - @client = DropboxClient.new(@access_token) - @access_token - rescue DropboxError, ArgumentError => ex - raise AuthError.new("validate_auth_code(): #{ex.message}", DATASOURCE_NAME) - end - # Validates the authorization callback # @param params : mixed def validate_callback(params) - session = {csrf_token: params[:state].split('|').first.presence } - @auth_flow = DropboxOAuth2Flow.new(@app_key, @app_secret, @callback_url, session, :csrf_token) - data = @auth_flow.finish(params) - @access_token = data[0] # Only keep the access token - @auth_flow = nil - @client = DropboxClient.new(@access_token) + raise "state doesn't match" unless params[:state] == state + auth_bearer = authenticator.get_token(params[:code], redirect_uri: @callback_url) + @access_token = auth_bearer.token + + @client = DropboxApi::Client.new(@access_token) @access_token - rescue DropboxError, ArgumentError => ex + rescue => ex raise AuthError.new("validate_callback(#{params.inspect}): #{ex.message}", DATASOURCE_NAME) end @@ -111,7 +90,7 @@ module CartoDB # @throws AuthError def token=(token) @access_token = token - @client = DropboxClient.new(@access_token) + @client = DropboxApi::Client.new(@access_token) rescue => ex handle_error(ex, "token= : #{ex.message}") end @@ -133,9 +112,9 @@ module CartoDB self.filter = filter @formats.each do |search_query| - response = @client.search('/', search_query) - response.each do |item| - all_results.push(format_item_data(item)) + response = @client.search(search_query, '') + response.matches.each do |item| + all_results.push(format_item_data(item.resource)) end end all_results @@ -150,8 +129,11 @@ module CartoDB # @throws AuthError # @throws DataDownloadError def get_resource(id) - contents, = @client.get_file_and_metadata(id) - contents + file_contents = '' + @client.download(id) do |chunk| + file_contents << chunk + end + file_contents rescue => ex handle_error(ex, "get_resource() #{id}: #{ex.message}") end @@ -164,7 +146,7 @@ module CartoDB def get_resource_metadata(id) raise DropboxPermissionError.new('No Dropbox client', DATASOURCE_NAME) unless @client.present? - response = @client.metadata(id) + response = @client.get_metadata(id) item_data = format_item_data(response) item_data.to_hash @@ -214,17 +196,13 @@ module CartoDB # @throws AuthError def token_valid? # Any call would do, we just want to see if communicates or refuses the token - @client.account_info + raise "Current account not found" unless @client.get_current_account true - rescue DropboxError => ex - error_code = ex.http_response.code.to_i - raise AuthError.new("token_valid? : #{ex.message}") unless (error_code == 401 || error_code == 403) - false end # Revokes current set token def revoke_token - @client.disable_access_token + @client.revoke true rescue => ex raise AuthError.new("revoke_token: #{ex.message}", DATASOURCE_NAME) @@ -239,7 +217,7 @@ module CartoDB # @throws AuthError # @throws mixed def handle_error(original_exception, message) - if original_exception.kind_of? DropboxError + if original_exception.kind_of? DropboxApi::Errors::BasicError error_code = original_exception.http_response.code.to_i if error_code == 401 || error_code == 403 raise TokenExpiredOrInvalidError.new(message, DATASOURCE_NAME) @@ -256,19 +234,28 @@ module CartoDB # Formats all data to comply with our desired format # @param item_data Hash : Single item returned from Dropbox API # @return { :id, :title, :url, :service, :size } - def format_item_data(item_data) - filename = item_data.fetch('path').split('/').last + def format_item_data(resource) + path = resource.path_display + filename = path.split('/').last { - id: item_data.fetch('path'), + id: path, title: filename, filename: filename, service: DATASOURCE_NAME, - checksum: checksum_of(item_data.fetch('rev')), - size: item_data.fetch('bytes').to_i + checksum: checksum_of(resource.rev), + size: resource.size } end + def authenticator + @authenticator ||= DropboxApi::Authenticator.new(@app_key, @app_secret) + end + + def state + service_name = service_name_for_user(DATASOURCE_NAME, @user) + CALLBACK_STATE_DATA_PLACEHOLDER.sub('user', @user.username).sub('service', service_name) + end end end end