From a68abe97ddb0daa64e8ce04b621ce1059a680206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 11:48:55 -0300 Subject: [PATCH 01/15] add configuration do RDStation --- lib/rdstation-ruby-client.rb | 2 ++ lib/rdstation.rb | 14 ++++++++++++++ rdstation-ruby-client.gemspec | 2 +- spec/lib/rdstation_spec.rb | 18 ++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 lib/rdstation.rb create mode 100644 spec/lib/rdstation_spec.rb diff --git a/lib/rdstation-ruby-client.rb b/lib/rdstation-ruby-client.rb index a0f2dc6..aafbe20 100644 --- a/lib/rdstation-ruby-client.rb +++ b/lib/rdstation-ruby-client.rb @@ -15,3 +15,5 @@ # Error handling require 'rdstation/error' require 'rdstation/error_handler' + +require 'rdstation' diff --git a/lib/rdstation.rb b/lib/rdstation.rb new file mode 100644 index 0000000..1a23ff6 --- /dev/null +++ b/lib/rdstation.rb @@ -0,0 +1,14 @@ +module RDStation + class << self + attr_accessor :configuration + + def configure + self.configuration ||= Configuration.new + yield(configuration) + end + end + + class Configuration + attr_accessor :client_id, :client_secret + end +end \ No newline at end of file diff --git a/rdstation-ruby-client.gemspec b/rdstation-ruby-client.gemspec index 72ce516..0e82a61 100644 --- a/rdstation-ruby-client.gemspec +++ b/rdstation-ruby-client.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.0.0' - spec.add_development_dependency "bundler", "~> 1.3" + spec.add_development_dependency "bundler", "> 1.3" spec.add_development_dependency "rake" spec.add_development_dependency 'rspec' spec.add_development_dependency 'webmock', '~> 2.1' diff --git a/spec/lib/rdstation_spec.rb b/spec/lib/rdstation_spec.rb new file mode 100644 index 0000000..c19eeed --- /dev/null +++ b/spec/lib/rdstation_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +RSpec.describe RDStation do + describe '.configure' do + let(:client_id) { 'client_id' } + let(:client_secret) { 'client_secret' } + + it 'sets the configuration' do + RDStation.configure do |config| + config.client_id = client_id + config.client_secret = client_secret + end + + expect(RDStation.configuration.client_id).to eq client_id + expect(RDStation.configuration.client_secret).to eq client_secret + end + end +end From 65501b778e13d413822b3c71c6f808e1adca4895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 13:46:12 -0300 Subject: [PATCH 02/15] use secrets in config on authentication client --- lib/rdstation/authentication.rb | 6 +- spec/lib/rdstation/authentication_spec.rb | 90 +++++++++++++++++++++++ 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/lib/rdstation/authentication.rb b/lib/rdstation/authentication.rb index ddf7431..7c4a50c 100644 --- a/lib/rdstation/authentication.rb +++ b/lib/rdstation/authentication.rb @@ -7,9 +7,9 @@ class Authentication DEFAULT_HEADERS = { 'Content-Type' => 'application/json' }.freeze REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze - def initialize(client_id, client_secret) - @client_id = client_id - @client_secret = client_secret + def initialize(client_id = nil, client_secret = nil) + @client_id = client_id || RDStation.configuration.client_id + @client_secret = client_secret || RDStation.configuration.client_secret end # diff --git a/spec/lib/rdstation/authentication_spec.rb b/spec/lib/rdstation/authentication_spec.rb index 05aebac..8c85420 100644 --- a/spec/lib/rdstation/authentication_spec.rb +++ b/spec/lib/rdstation/authentication_spec.rb @@ -88,6 +88,34 @@ let(:authentication) { described_class.new('client_id', 'client_secret') } + describe '#auth_url' do + let(:configuration_client_id) { 'configuration_client_id' } + let(:configuration_client_secret) { 'configuration_client_secret' } + let(:redirect_url) { 'redirect_url' } + before do + RDStation.configure do |config| + config.client_id = configuration_client_id + config.client_secret = configuration_client_secret + end + end + + context 'when client_id and client_secret are specified in initialization' do + it 'uses those specified in initialization' do + auth = described_class.new('initialization_client_id', 'initialization_client_secret') + expected = "https://api.rd.services/auth/dialog?client_id=initialization_client_id&redirect_url=#{redirect_url}" + expect(auth.auth_url(redirect_url)).to eq expected + end + end + + context 'when client_id and client_secret are specified only in configuration' do + it 'uses those specified in configuration' do + auth = described_class.new + expected = "https://api.rd.services/auth/dialog?client_id=#{configuration_client_id}&redirect_url=#{redirect_url}" + expect(auth.auth_url(redirect_url)).to eq expected + end + end + end + describe '#authenticate' do context 'when the code is valid' do before do @@ -138,6 +166,37 @@ end.to raise_error(RDStation::Error::ExpiredCodeGrant) end end + + context 'when client_id and client_secret are specified only in configuration' do + let(:authentication) { described_class.new } + let(:configuration_client_id) { 'configuration_client_id' } + let(:configuration_client_secret) { 'configuration_client_secret' } + let(:token_request_with_valid_code_secrets_from_config) do + { + client_id: configuration_client_id, + client_secret: configuration_client_secret, + code: 'valid_code' + } + end + before do + RDStation.configure do |config| + config.client_id = configuration_client_id + config.client_secret = configuration_client_secret + end + + stub_request(:post, token_endpoint) + .with( + headers: request_headers, + body: token_request_with_valid_code_secrets_from_config.to_json + ) + .to_return(credentials_response) + end + + it 'returns the credentials' do + credentials_request = authentication.authenticate('valid_code') + expect(credentials_request).to eq(credentials) + end + end end describe '#update_access_token' do @@ -173,6 +232,37 @@ end.to raise_error(RDStation::Error::InvalidCredentials) end end + + context 'when client_id and client_secret are specified only in configuration' do + let(:authentication) { described_class.new } + let(:configuration_client_id) { 'configuration_client_id' } + let(:configuration_client_secret) { 'configuration_client_secret' } + let(:token_request_with_valid_refresh_code_secrets_from_config) do + { + client_id: configuration_client_id, + client_secret: configuration_client_secret, + refresh_token: 'valid_refresh_token' + } + end + before do + RDStation.configure do |config| + config.client_id = configuration_client_id + config.client_secret = configuration_client_secret + end + + stub_request(:post, token_endpoint) + .with( + headers: request_headers, + body: token_request_with_valid_refresh_code_secrets_from_config.to_json + ) + .to_return(credentials_response) + end + + it 'returns the credentials' do + credentials_request = authentication.update_access_token('valid_refresh_token') + expect(credentials_request).to eq(credentials) + end + end end describe ".revoke" do From aeea24cb5843e3dc7e0be4784645c5e0599049de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 14:04:01 -0300 Subject: [PATCH 03/15] rename authorization_header to authorization' --- lib/rdstation-ruby-client.rb | 2 +- .../{authorization_header.rb => authorization.rb} | 4 ++-- lib/rdstation/client.rb | 12 ++++++------ lib/rdstation/contacts.rb | 14 +++++++------- lib/rdstation/events.rb | 6 +++--- lib/rdstation/fields.rb | 6 +++--- lib/rdstation/webhooks.rb | 14 +++++++------- ...zation_header_spec.rb => authorization_spec.rb} | 6 +++--- spec/lib/rdstation/client_spec.rb | 12 ++++++------ spec/lib/rdstation/contacts_spec.rb | 6 +++--- spec/lib/rdstation/events_spec.rb | 6 +++--- spec/lib/rdstation/fields_spec.rb | 2 +- spec/lib/rdstation/webhooks_spec.rb | 2 +- 13 files changed, 46 insertions(+), 46 deletions(-) rename lib/rdstation/{authorization_header.rb => authorization.rb} (91%) rename spec/lib/rdstation/{authorization_header_spec.rb => authorization_spec.rb} (78%) diff --git a/lib/rdstation-ruby-client.rb b/lib/rdstation-ruby-client.rb index aafbe20..bfe24db 100644 --- a/lib/rdstation-ruby-client.rb +++ b/lib/rdstation-ruby-client.rb @@ -5,7 +5,7 @@ # API requests require 'rdstation/authentication' -require 'rdstation/authorization_header' +require 'rdstation/authorization' require 'rdstation/client' require 'rdstation/contacts' require 'rdstation/events' diff --git a/lib/rdstation/authorization_header.rb b/lib/rdstation/authorization.rb similarity index 91% rename from lib/rdstation/authorization_header.rb rename to lib/rdstation/authorization.rb index 2a41e31..acbaee1 100644 --- a/lib/rdstation/authorization_header.rb +++ b/lib/rdstation/authorization.rb @@ -1,12 +1,12 @@ module RDStation - class AuthorizationHeader + class Authorization def initialize(access_token:) @access_token = access_token validate_access_token access_token end - def to_h + def headers { "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" } end diff --git a/lib/rdstation/client.rb b/lib/rdstation/client.rb index 63c956b..9825995 100644 --- a/lib/rdstation/client.rb +++ b/lib/rdstation/client.rb @@ -1,23 +1,23 @@ module RDStation class Client - def initialize(access_token:) - @authorization_header = AuthorizationHeader.new(access_token: access_token) + def initialize(access_token:, refresh_token: nil) + @authorization = Authorization.new(access_token: access_token) end def contacts - @contacts ||= RDStation::Contacts.new(authorization_header: @authorization_header) + @contacts ||= RDStation::Contacts.new(authorization: @authorization) end def events - @events ||= RDStation::Events.new(authorization_header: @authorization_header) + @events ||= RDStation::Events.new(authorization: @authorization) end def fields - @fields ||= RDStation::Fields.new(authorization_header: @authorization_header) + @fields ||= RDStation::Fields.new(authorization: @authorization) end def webhooks - @webhooks ||= RDStation::Webhooks.new(authorization_header: @authorization_header) + @webhooks ||= RDStation::Webhooks.new(authorization: @authorization) end end end diff --git a/lib/rdstation/contacts.rb b/lib/rdstation/contacts.rb index 6668983..5cbd8fc 100644 --- a/lib/rdstation/contacts.rb +++ b/lib/rdstation/contacts.rb @@ -4,8 +4,8 @@ module RDStation class Contacts include HTTParty - def initialize(authorization_header:) - @authorization_header = authorization_header + def initialize(authorization:) + @authorization = authorization end # @@ -13,12 +13,12 @@ def initialize(authorization_header:) # The unique uuid associated to each RD Station Contact. # def by_uuid(uuid) - response = self.class.get(base_url(uuid), headers: @authorization_header.to_h) + response = self.class.get(base_url(uuid), headers: @authorization.headers) ApiResponse.build(response) end def by_email(email) - response = self.class.get(base_url("email:#{email}"), headers: @authorization_header.to_h) + response = self.class.get(base_url("email:#{email}"), headers: @authorization.headers) ApiResponse.build(response) end @@ -34,7 +34,7 @@ def by_email(email) # :website # :tags def update(uuid, contact_hash) - response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => @authorization_header.to_h) + response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => @authorization.headers) ApiResponse.build(response) end @@ -48,13 +48,13 @@ def update(uuid, contact_hash) # def upsert(identifier, identifier_value, contact_hash) path = "#{identifier}:#{identifier_value}" - response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: @authorization_header.to_h) + response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: @authorization.headers) ApiResponse.build(response) end private - def base_url(path = "") + def base_url(path = '') "https://api.rd.services/platform/contacts/#{path}" end end diff --git a/lib/rdstation/events.rb b/lib/rdstation/events.rb index bb6d4bb..66c2c61 100644 --- a/lib/rdstation/events.rb +++ b/lib/rdstation/events.rb @@ -4,12 +4,12 @@ class Events EVENTS_ENDPOINT = 'https://api.rd.services/platform/events'.freeze - def initialize(authorization_header:) - @authorization_header = authorization_header + def initialize(authorization:) + @authorization = authorization end def create(payload) - response = self.class.post(EVENTS_ENDPOINT, headers: @authorization_header.to_h, body: payload.to_json) + response = self.class.post(EVENTS_ENDPOINT, headers: @authorization.headers, body: payload.to_json) response_body = JSON.parse(response.body) return response_body unless errors?(response_body) RDStation::ErrorHandler.new(response).raise_error diff --git a/lib/rdstation/fields.rb b/lib/rdstation/fields.rb index 2a7b5a0..45da314 100644 --- a/lib/rdstation/fields.rb +++ b/lib/rdstation/fields.rb @@ -6,12 +6,12 @@ class Fields BASE_URL = 'https://api.rd.services/platform/contacts/fields'.freeze - def initialize(authorization_header:) - @authorization_header = authorization_header + def initialize(authorization:) + @authorization = authorization end def all - response = self.class.get(BASE_URL, headers: @authorization_header.to_h) + response = self.class.get(BASE_URL, headers: @authorization.headers) ApiResponse.build(response) end diff --git a/lib/rdstation/webhooks.rb b/lib/rdstation/webhooks.rb index daa8c0d..061a221 100644 --- a/lib/rdstation/webhooks.rb +++ b/lib/rdstation/webhooks.rb @@ -2,32 +2,32 @@ module RDStation class Webhooks include HTTParty - def initialize(authorization_header:) - @authorization_header = authorization_header + def initialize(authorization:) + @authorization = authorization end def all - response = self.class.get(base_url, headers: @authorization_header.to_h) + response = self.class.get(base_url, headers: @authorization.headers) ApiResponse.build(response) end def by_uuid(uuid) - response = self.class.get(base_url(uuid), headers: @authorization_header.to_h) + response = self.class.get(base_url(uuid), headers: @authorization.headers) ApiResponse.build(response) end def create(payload) - response = self.class.post(base_url, headers: @authorization_header.to_h, body: payload.to_json) + response = self.class.post(base_url, headers: @authorization.headers, body: payload.to_json) ApiResponse.build(response) end def update(uuid, payload) - response = self.class.put(base_url(uuid), headers: @authorization_header.to_h, body: payload.to_json) + response = self.class.put(base_url(uuid), headers: @authorization.headers, body: payload.to_json) ApiResponse.build(response) end def delete(uuid) - response = self.class.delete(base_url(uuid), headers: @authorization_header.to_h) + response = self.class.delete(base_url(uuid), headers: @authorization.headers) return webhook_deleted_message unless response.body RDStation::ErrorHandler.new(response).raise_error end diff --git a/spec/lib/rdstation/authorization_header_spec.rb b/spec/lib/rdstation/authorization_spec.rb similarity index 78% rename from spec/lib/rdstation/authorization_header_spec.rb rename to spec/lib/rdstation/authorization_spec.rb index 436e149..7d3a28d 100644 --- a/spec/lib/rdstation/authorization_header_spec.rb +++ b/spec/lib/rdstation/authorization_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -RSpec.describe RDStation::AuthorizationHeader do +RSpec.describe RDStation::Authorization do describe ".initialize" do context "when access_token is nil" do @@ -12,11 +12,11 @@ end end - describe "#to_h" do + describe "#headers" do let(:access_token) { 'access_token' } it "generates the correct header" do - header = described_class.new(access_token: access_token).to_h + header = described_class.new(access_token: access_token).headers expect(header['Authorization']).to eq "Bearer #{access_token}" expect(header['Content-Type']).to eq "application/json" end diff --git a/spec/lib/rdstation/client_spec.rb b/spec/lib/rdstation/client_spec.rb index f0aeb68..4d23eb0 100644 --- a/spec/lib/rdstation/client_spec.rb +++ b/spec/lib/rdstation/client_spec.rb @@ -4,27 +4,27 @@ context "when access_token is given" do let(:access_token) { 'access_token' } let(:client) { described_class.new(access_token: access_token) } - let(:mock_authorization_header) { double(RDStation::AuthorizationHeader) } + let(:mock_authorization) { double(RDStation::Authorization) } - before { allow(RDStation::AuthorizationHeader).to receive(:new).and_return mock_authorization_header } + before { allow(RDStation::Authorization).to receive(:new).and_return mock_authorization } it 'returns Contacts endpoint' do - expect(RDStation::Contacts).to receive(:new).with({ authorization_header: mock_authorization_header }).and_call_original + expect(RDStation::Contacts).to receive(:new).with({ authorization: mock_authorization }).and_call_original expect(client.contacts).to be_instance_of RDStation::Contacts end it 'returns Events endpoint' do - expect(RDStation::Events).to receive(:new).with({ authorization_header: mock_authorization_header }).and_call_original + expect(RDStation::Events).to receive(:new).with({ authorization: mock_authorization }).and_call_original expect(client.events).to be_instance_of RDStation::Events end it 'returns Fields endpoint' do - expect(RDStation::Fields).to receive(:new).with({ authorization_header: mock_authorization_header }).and_call_original + expect(RDStation::Fields).to receive(:new).with({ authorization: mock_authorization }).and_call_original expect(client.fields).to be_instance_of RDStation::Fields end it 'returns Webhooks endpoint' do - expect(RDStation::Webhooks).to receive(:new).with({ authorization_header: mock_authorization_header }).and_call_original + expect(RDStation::Webhooks).to receive(:new).with({ authorization: mock_authorization }).and_call_original expect(client.webhooks).to be_instance_of RDStation::Webhooks end end diff --git a/spec/lib/rdstation/contacts_spec.rb b/spec/lib/rdstation/contacts_spec.rb index 677e7f5..d4c2cee 100644 --- a/spec/lib/rdstation/contacts_spec.rb +++ b/spec/lib/rdstation/contacts_spec.rb @@ -16,13 +16,13 @@ let(:expired_access_token) { 'expired_access_token' } let(:contact_with_valid_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: valid_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: valid_access_token)) end let(:contact_with_expired_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: expired_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: expired_access_token)) end let(:contact_with_invalid_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: invalid_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: invalid_access_token)) end diff --git a/spec/lib/rdstation/events_spec.rb b/spec/lib/rdstation/events_spec.rb index a81a2af..c2088e4 100644 --- a/spec/lib/rdstation/events_spec.rb +++ b/spec/lib/rdstation/events_spec.rb @@ -6,13 +6,13 @@ let(:expired_access_token) { 'expired_access_token' } let(:event_with_valid_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: valid_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: valid_access_token)) end let(:event_with_expired_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: expired_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: expired_access_token)) end let(:event_with_invalid_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: invalid_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: invalid_access_token)) end let(:events_endpoint) { 'https://api.rd.services/platform/events' } diff --git a/spec/lib/rdstation/fields_spec.rb b/spec/lib/rdstation/fields_spec.rb index 9769ad6..4f60c34 100644 --- a/spec/lib/rdstation/fields_spec.rb +++ b/spec/lib/rdstation/fields_spec.rb @@ -3,7 +3,7 @@ RSpec.describe RDStation::Fields do let(:valid_access_token) { 'valid_access_token' } let(:rdstation_fields_with_valid_token) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: valid_access_token)) + described_class.new(authorization: RDStation::Authorization.new(access_token: valid_access_token)) end let(:valid_headers) do diff --git a/spec/lib/rdstation/webhooks_spec.rb b/spec/lib/rdstation/webhooks_spec.rb index f42839f..35787e1 100644 --- a/spec/lib/rdstation/webhooks_spec.rb +++ b/spec/lib/rdstation/webhooks_spec.rb @@ -2,7 +2,7 @@ RSpec.describe RDStation::Webhooks do let(:webhooks_client) do - described_class.new(authorization_header: RDStation::AuthorizationHeader.new(access_token: 'access_token')) + described_class.new(authorization: RDStation::Authorization.new(access_token: 'access_token')) end let(:webhooks_endpoint) { 'https://api.rd.services/integrations/webhooks/' } From c3cfd936dbfdda3c3dbe84980861dc9ba1e8d343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 16:40:52 -0300 Subject: [PATCH 04/15] add retryable request --- lib/rdstation-ruby-client.rb | 4 +- lib/rdstation/authorization.rb | 14 ++- lib/rdstation/retryable_request.rb | 34 ++++++ rdstation-ruby-client.gemspec | 1 + spec/lib/rdstation/retryable_request_spec.rb | 112 +++++++++++++++++++ 5 files changed, 157 insertions(+), 8 deletions(-) create mode 100644 lib/rdstation/retryable_request.rb create mode 100644 spec/lib/rdstation/retryable_request_spec.rb diff --git a/lib/rdstation-ruby-client.rb b/lib/rdstation-ruby-client.rb index bfe24db..6d34809 100644 --- a/lib/rdstation-ruby-client.rb +++ b/lib/rdstation-ruby-client.rb @@ -4,6 +4,8 @@ require 'rdstation/api_response' # API requests +require 'rdstation' +require 'rdstation/retryable_request' require 'rdstation/authentication' require 'rdstation/authorization' require 'rdstation/client' @@ -15,5 +17,3 @@ # Error handling require 'rdstation/error' require 'rdstation/error_handler' - -require 'rdstation' diff --git a/lib/rdstation/authorization.rb b/lib/rdstation/authorization.rb index acbaee1..7eb6bea 100644 --- a/lib/rdstation/authorization.rb +++ b/lib/rdstation/authorization.rb @@ -1,21 +1,23 @@ module RDStation class Authorization - - def initialize(access_token:) + attr_reader :refresh_token + attr_accessor :access_token + def initialize(access_token:, refresh_token: nil) @access_token = access_token + @refresh_token = refresh_token validate_access_token access_token end - + def headers { "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" } end - + private - + def validate_access_token(access_token) access_token_msg = ':access_token is required' raise ArgumentError, access_token_msg unless access_token end - + end end \ No newline at end of file diff --git a/lib/rdstation/retryable_request.rb b/lib/rdstation/retryable_request.rb new file mode 100644 index 0000000..e93e4f1 --- /dev/null +++ b/lib/rdstation/retryable_request.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module RDStation + module RetryableRequest + MAX_RETRIES = 1 + def retryable_request(authorization) + retries = 0 + begin + yield authorization + rescue ::RDStation::Error::ExpiredAccessToken => e + raise if !retry_possible?(authorization) || retries >= MAX_RETRIES + + retries += 1 + refresh_access_token(authorization) + retry + end + end + + def retry_possible?(authorization) + [ + RDStation.configuration&.client_id, + RDStation.configuration&.client_secret, + authorization.refresh_token + ].all? + end + + def refresh_access_token(authorization) + client = RDStation::Authentication.new + response = client.update_access_token(authorization.refresh_token) + authorization.access_token = response['access_token'] + # notify new access token + end + end +end \ No newline at end of file diff --git a/rdstation-ruby-client.gemspec b/rdstation-ruby-client.gemspec index 0e82a61..eafd1ec 100644 --- a/rdstation-ruby-client.gemspec +++ b/rdstation-ruby-client.gemspec @@ -26,6 +26,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'webmock', '~> 2.1' spec.add_development_dependency 'turn' spec.add_development_dependency 'rspec_junit_formatter' + spec.add_development_dependency 'pry' spec.add_dependency "httparty", "~> 0.12" end diff --git a/spec/lib/rdstation/retryable_request_spec.rb b/spec/lib/rdstation/retryable_request_spec.rb new file mode 100644 index 0000000..6bbb4d8 --- /dev/null +++ b/spec/lib/rdstation/retryable_request_spec.rb @@ -0,0 +1,112 @@ +require 'spec_helper' + +class DummyClass + include ::RDStation::RetryableRequest +end + +RSpec.describe RDStation::RetryableRequest do + let(:subject) { DummyClass.new } + describe '.retryable_request' do + context 'when authorization has a valid refresh_token and config is provided' do + let (:access_token) { 'access_token' } + let (:refresh_token) { 'refresh_token' } + let (:auth) do + ::RDStation::Authorization.new(access_token: access_token, + refresh_token: refresh_token + ) + end + context 'original request was successful' do + it 'yields control to the given block' do + expect do |block| + subject.retryable_request(auth, &block) + end.to yield_with_args(auth) + end + end + + context 'original request raised a retryable exception' do + let (:auth_new_access_token) do + ::RDStation::Authorization.new(access_token: 'new access_token', + refresh_token: refresh_token + ) + end + + let(:new_credentials) do + { + 'access_token' => 'new access_token', + 'expires_in' => 86_400, + 'refresh_token' => refresh_token + } + end + let(:authentication_client) {instance_double(::RDStation::Authentication) } + + before do + RDStation.configure do |config| + config.client_id = "123" + config.client_secret = "312" + end + allow(::RDStation::Authentication).to receive(:new) + .with(no_args) + .and_return(authentication_client) + allow(authentication_client).to receive(:update_access_token) + .with(auth.refresh_token). + and_return(new_credentials) + end + + it 'refreshes the access_token and retries the request' do + dummy_request = double("dummy_request") + expect(dummy_request).to receive(:call).twice do |auth| + expired_token = ::RDStation::Error::ExpiredAccessToken.new({'error_message' => 'x'}) + raise expired_token unless auth.access_token == new_credentials['access_token'] + end + + expect do + subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } + end.not_to raise_error + end + + context 'and keeps raising retryable exception event after token refreshed' do + it 'retries only once' do + dummy_request = double("dummy_request") + expect(dummy_request).to receive(:call).twice do |_| + raise ::RDStation::Error::ExpiredAccessToken.new({'error_message' => 'x'}) + end + + expect do + subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } + end.to raise_error ::RDStation::Error::ExpiredAccessToken + end + end + end + + context 'original request raised a non retryable exception' do + it 'raises error' do + dummy_request = double("dummy_request") + expect(dummy_request).to receive(:call).once do |_| + raise RuntimeError.new("a non retryable error") + end + + expect do + subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } + end.to raise_error RuntimeError + end + end + end + + context 'all legacy scenarios' do + let (:access_token) { 'access_token' } + let (:auth) { ::RDStation::Authorization.new(access_token: access_token) } + + it 'implement me' do + dummy_request = double("dummy_request") + expect(dummy_request).to receive(:call).once do |_| + raise ::RDStation::Error::ExpiredAccessToken.new({'error_message' => 'x'}) + end + + expect do + subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } + end.to raise_error ::RDStation::Error::ExpiredAccessToken + end + end + + end +end \ No newline at end of file From 74b90f24a3c29c365c26568ba78570f526564e76 Mon Sep 17 00:00:00 2001 From: Almir Mendes Date: Thu, 12 Dec 2019 17:57:13 -0300 Subject: [PATCH 05/15] Apply retryable_request in contacts requests --- lib/rdstation/contacts.rb | 29 +++++++++++++++++++---------- spec/lib/rdstation/contacts_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/rdstation/contacts.rb b/lib/rdstation/contacts.rb index 5cbd8fc..957f0e3 100644 --- a/lib/rdstation/contacts.rb +++ b/lib/rdstation/contacts.rb @@ -3,7 +3,8 @@ module RDStation # More info: https://developers.rdstation.com/pt-BR/reference/contacts class Contacts include HTTParty - + include ::RDStation::RetryableRequest + def initialize(authorization:) @authorization = authorization end @@ -13,13 +14,17 @@ def initialize(authorization:) # The unique uuid associated to each RD Station Contact. # def by_uuid(uuid) - response = self.class.get(base_url(uuid), headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.get(base_url(uuid), headers: authorization.headers) + ApiResponse.build(response) + end end def by_email(email) - response = self.class.get(base_url("email:#{email}"), headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.get(base_url("email:#{email}"), headers: authorization.headers) + ApiResponse.build(response) + end end # The Contact hash may contain the following parameters: @@ -34,8 +39,10 @@ def by_email(email) # :website # :tags def update(uuid, contact_hash) - response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => authorization.headers) + ApiResponse.build(response) + end end # @@ -47,9 +54,11 @@ def update(uuid, contact_hash) # Contact data # def upsert(identifier, identifier_value, contact_hash) - path = "#{identifier}:#{identifier_value}" - response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + path = "#{identifier}:#{identifier_value}" + response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: authorization.headers) + ApiResponse.build(response) + end end private diff --git a/spec/lib/rdstation/contacts_spec.rb b/spec/lib/rdstation/contacts_spec.rb index d4c2cee..514e8e6 100644 --- a/spec/lib/rdstation/contacts_spec.rb +++ b/spec/lib/rdstation/contacts_spec.rb @@ -109,6 +109,11 @@ end describe '#by_uuid' do + it 'calls retryable_request' do + expect(contact_with_valid_token).to receive(:retryable_request) + contact_with_valid_token.by_uuid('valid_uuid') + end + context 'with a valid auth token' do context 'when the contact exists' do let(:contact) do @@ -172,6 +177,11 @@ end describe '#by_email' do + it 'calls retryable_request' do + expect(contact_with_valid_token).to receive(:retryable_request) + contact_with_valid_token.by_email('x@xpto.com') + end + context 'with a valid auth token' do context 'when the contact exists' do let(:contact) do @@ -235,6 +245,11 @@ end describe '#update' do + it 'calls retryable_request' do + expect(contact_with_valid_token).to receive(:retryable_request) + contact_with_valid_token.update('valid_uuid', {}) + end + context 'with a valid access_token' do let(:valid_access_token) { 'valid_access_token' } let(:headers) do @@ -322,6 +337,11 @@ end describe '#upsert' do + it 'calls retryable_request' do + expect(contact_with_valid_token).to receive(:retryable_request) + contact_with_valid_token.upsert('email', 'valid@email.com', {}) + end + context 'with a valid access_token' do let(:valid_access_token) { 'valid_access_token' } From 6ebf1ed26587f8b508de6a2d2149a3f558fc5ce8 Mon Sep 17 00:00:00 2001 From: antonio-muniz Date: Thu, 12 Dec 2019 18:12:04 -0300 Subject: [PATCH 06/15] Add callback for when access token is refreshed --- lib/rdstation.rb | 7 +++- lib/rdstation/client.rb | 2 +- lib/rdstation/retryable_request.rb | 4 +-- spec/lib/rdstation/retryable_request_spec.rb | 38 +++++++++++++++++--- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/lib/rdstation.rb b/lib/rdstation.rb index 1a23ff6..9de5997 100644 --- a/lib/rdstation.rb +++ b/lib/rdstation.rb @@ -10,5 +10,10 @@ def configure class Configuration attr_accessor :client_id, :client_secret + attr_reader :access_token_refresh_callback + + def on_access_token_refresh(&block) + @access_token_refresh_callback = block + end end -end \ No newline at end of file +end diff --git a/lib/rdstation/client.rb b/lib/rdstation/client.rb index 9825995..438f2b8 100644 --- a/lib/rdstation/client.rb +++ b/lib/rdstation/client.rb @@ -3,7 +3,7 @@ class Client def initialize(access_token:, refresh_token: nil) @authorization = Authorization.new(access_token: access_token) end - + def contacts @contacts ||= RDStation::Contacts.new(authorization: @authorization) end diff --git a/lib/rdstation/retryable_request.rb b/lib/rdstation/retryable_request.rb index e93e4f1..1a13884 100644 --- a/lib/rdstation/retryable_request.rb +++ b/lib/rdstation/retryable_request.rb @@ -28,7 +28,7 @@ def refresh_access_token(authorization) client = RDStation::Authentication.new response = client.update_access_token(authorization.refresh_token) authorization.access_token = response['access_token'] - # notify new access token + RDStation.configuration&.access_token_refresh_callback&.call(authorization) end end -end \ No newline at end of file +end diff --git a/spec/lib/rdstation/retryable_request_spec.rb b/spec/lib/rdstation/retryable_request_spec.rb index 6bbb4d8..73a8c59 100644 --- a/spec/lib/rdstation/retryable_request_spec.rb +++ b/spec/lib/rdstation/retryable_request_spec.rb @@ -9,6 +9,7 @@ class DummyClass describe '.retryable_request' do context 'when authorization has a valid refresh_token and config is provided' do let (:access_token) { 'access_token' } + let (:new_access_token) { 'new_access_token' } let (:refresh_token) { 'refresh_token' } let (:auth) do ::RDStation::Authorization.new(access_token: access_token, @@ -25,14 +26,14 @@ class DummyClass context 'original request raised a retryable exception' do let (:auth_new_access_token) do - ::RDStation::Authorization.new(access_token: 'new access_token', + ::RDStation::Authorization.new(access_token: new_access_token, refresh_token: refresh_token ) end let(:new_credentials) do { - 'access_token' => 'new access_token', + 'access_token' => new_access_token, 'expires_in' => 86_400, 'refresh_token' => refresh_token } @@ -43,6 +44,9 @@ class DummyClass RDStation.configure do |config| config.client_id = "123" config.client_secret = "312" + config.on_access_token_refresh do + 'callback code' + end end allow(::RDStation::Authentication).to receive(:new) .with(no_args) @@ -56,9 +60,15 @@ class DummyClass dummy_request = double("dummy_request") expect(dummy_request).to receive(:call).twice do |auth| expired_token = ::RDStation::Error::ExpiredAccessToken.new({'error_message' => 'x'}) - raise expired_token unless auth.access_token == new_credentials['access_token'] + raise expired_token unless auth.access_token == new_access_token end + expect(RDStation.configuration.access_token_refresh_callback) + .to receive(:call) + .once do |authorization| + expect(authorization.access_token).to eq new_access_token + end + expect do subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } end.not_to raise_error @@ -76,6 +86,26 @@ class DummyClass end.to raise_error ::RDStation::Error::ExpiredAccessToken end end + + context 'and access token refresh callback is not set' do + before do + RDStation.configure do |config| + config.on_access_token_refresh(&nil) + end + end + + it 'executes the refresh and retry without raising an error' do + dummy_request = double("dummy_request") + expect(dummy_request).to receive(:call).twice do |auth| + expired_token = ::RDStation::Error::ExpiredAccessToken.new({'error_message' => 'x'}) + raise expired_token unless auth.access_token == new_access_token + end + + expect do + subject.retryable_request(auth) { |yielded_auth| dummy_request.call(yielded_auth) } + end.not_to raise_error + end + end end context 'original request raised a non retryable exception' do @@ -109,4 +139,4 @@ class DummyClass end end -end \ No newline at end of file +end From 473caec49bcca154b8828ab546d9d9ed5faaaddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 18:44:15 -0300 Subject: [PATCH 07/15] apply retryable request in events, fields and webhooks --- lib/rdstation/events.rb | 11 +++++++---- lib/rdstation/fields.rb | 7 +++++-- lib/rdstation/webhooks.rb | 34 +++++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/lib/rdstation/events.rb b/lib/rdstation/events.rb index 66c2c61..420368a 100644 --- a/lib/rdstation/events.rb +++ b/lib/rdstation/events.rb @@ -1,6 +1,7 @@ module RDStation class Events include HTTParty + include ::RDStation::RetryableRequest EVENTS_ENDPOINT = 'https://api.rd.services/platform/events'.freeze @@ -9,10 +10,12 @@ def initialize(authorization:) end def create(payload) - response = self.class.post(EVENTS_ENDPOINT, headers: @authorization.headers, body: payload.to_json) - response_body = JSON.parse(response.body) - return response_body unless errors?(response_body) - RDStation::ErrorHandler.new(response).raise_error + retryable_request(@authorization) do |authorization| + response = self.class.post(EVENTS_ENDPOINT, headers: authorization.headers, body: payload.to_json) + response_body = JSON.parse(response.body) + return response_body unless errors?(response_body) + RDStation::ErrorHandler.new(response).raise_error + end end private diff --git a/lib/rdstation/fields.rb b/lib/rdstation/fields.rb index 45da314..130fde9 100644 --- a/lib/rdstation/fields.rb +++ b/lib/rdstation/fields.rb @@ -3,6 +3,7 @@ module RDStation # More info: https://developers.rdstation.com/pt-BR/reference/contacts class Fields include HTTParty + include ::RDStation::RetryableRequest BASE_URL = 'https://api.rd.services/platform/contacts/fields'.freeze @@ -11,8 +12,10 @@ def initialize(authorization:) end def all - response = self.class.get(BASE_URL, headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.get(BASE_URL, headers: authorization.headers) + ApiResponse.build(response) + end end end diff --git a/lib/rdstation/webhooks.rb b/lib/rdstation/webhooks.rb index 061a221..f3f2330 100644 --- a/lib/rdstation/webhooks.rb +++ b/lib/rdstation/webhooks.rb @@ -1,35 +1,47 @@ module RDStation class Webhooks include HTTParty + include ::RDStation::RetryableRequest def initialize(authorization:) @authorization = authorization end def all - response = self.class.get(base_url, headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.get(base_url, headers: authorization.headers) + ApiResponse.build(response) + end end def by_uuid(uuid) - response = self.class.get(base_url(uuid), headers: @authorization.headers) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.get(base_url(uuid), headers: authorization.headers) + ApiResponse.build(response) + end end def create(payload) - response = self.class.post(base_url, headers: @authorization.headers, body: payload.to_json) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.post(base_url, headers: authorization.headers, body: payload.to_json) + ApiResponse.build(response) + end end def update(uuid, payload) - response = self.class.put(base_url(uuid), headers: @authorization.headers, body: payload.to_json) - ApiResponse.build(response) + retryable_request(@authorization) do |authorization| + response = self.class.put(base_url(uuid), headers: authorization.headers, body: payload.to_json) + ApiResponse.build(response) + end end def delete(uuid) - response = self.class.delete(base_url(uuid), headers: @authorization.headers) - return webhook_deleted_message unless response.body - RDStation::ErrorHandler.new(response).raise_error + retryable_request(@authorization) do |authorization| + response = self.class.delete(base_url(uuid), headers: authorization.headers) + return webhook_deleted_message unless response.body + + RDStation::ErrorHandler.new(response).raise_error + end end private From 1721e66d818fb1b2f58226933404970f2d41f4ae Mon Sep 17 00:00:00 2001 From: antonio-muniz Date: Thu, 12 Dec 2019 19:05:00 -0300 Subject: [PATCH 08/15] Assign refresh token to authorization on client constructor --- lib/rdstation/client.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/rdstation/client.rb b/lib/rdstation/client.rb index 438f2b8..13296e8 100644 --- a/lib/rdstation/client.rb +++ b/lib/rdstation/client.rb @@ -1,7 +1,10 @@ module RDStation class Client def initialize(access_token:, refresh_token: nil) - @authorization = Authorization.new(access_token: access_token) + @authorization = Authorization.new( + access_token: access_token, + refresh_token: refresh_token + ) end def contacts From 7d2c33b1f00d3727a963dc195e97cd6afb858b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 19:58:37 -0300 Subject: [PATCH 09/15] update docs --- CHANGELOG.md | 41 +++++++++++++++++++++++++++++ README.md | 73 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 93 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0558cf6..39e12dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,44 @@ +## 2.2.0 + +### Additions + +#### Configuration + +Now it is possible to configure global params like client_id and client_secret only once, so you don't need to provide them to `RDStation::Authentication` every time. + +This can be done in the following way: + +```ruby +RDStation.configure do |config| + config.client_id = YOUR_CLIENT_ID + config.client_secret = YOUR_CLIENT_SECRET +end +``` + +If you're using Rails, this can be done in `config/initializers`. + +#### Automatic refresh of access_tokens + +When an access_token expires, a new one will be obtained automatically and the request will be made again. + +For this to work, you have to use `RDStation.configure` as described above, and provide the refresh token when instantiating `RDStation::Client` (ex: RDStation::Client.new(access_token: MY_ACCESS_TOKEN, refresh_token: MY_REFRESH_TOKEN). + +You can keep track of access_token changes, by providing a callback block inconfiguration. This block will be called with an `RDStation::Authorization` object, which contains the updated `access_token` and `refresh_token`. For example: + +```ruby +RDStation.configure do |config| + config.on_access_token_refresh do |authorization| + MyStoredAuth.where(refresh_token: authorization.refresh_token).update_all(access_token: authorization.access_token) + end +end +``` + +### Deprecations + +Providing `client_id` and `client_secret` directly to `RDStation::Authentication.new` is deprecated and will be removed in future versions. Use `RDStation.confifure` instead. + +Specifying refresh_token in `RDStation::Client.new(access_token: 'at', refresh_token: 'rt'` is optional right now, but will be mandatory in future versions. + ## 2.1.1 - Fixed a bug in error handling (issue [#47](https://github.com/ResultadosDigitais/rdstation-ruby-client/issues/47)) diff --git a/README.md b/README.md index c01b04a..7df40fc 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ Upgrading? Check the [migration guide](#Migration-guide) before bumping to a new 1. [Installation](#Installation) 2. [Usage](#Usage) - 1. [Authentication](#Authentication) - 2. [Contacts](#Contacts) - 3. [Events](#Events) - 4. [Fields](#Fields) - 5. [Webhooks](#Webhooks) - 6. [Errors](#Errors) + 1. [Configuration](#Configuration) + 2. [Authentication](#Authentication) + 3. [Contacts](#Contacts) + 4. [Events](#Events) + 5. [Fields](#Fields) + 6. [Webhooks](#Webhooks) + 7. [Errors](#Errors) 3. [Changelog](#Changelog) 4. [Migration guide](#Migration-guide) 1. [Upgrading from 1.2.x to 2.0.0](#Upgrading-from-1.2.x-to-2.0.0) @@ -39,6 +40,19 @@ Or install it yourself as: ## Usage +### Configuration + +Before getting youre credentials, you need to configure client_id and client_secret as following: + +```ruby +RDStation.configure do |config| + config.client_id = YOUR_CLIENT_ID + config.client_secret = YOUR_CLIENT_SECRET +end +``` + +For details on what `client_id` and `client_secret` are, check the [developers portal](https://developers.rdstation.com/en/authentication). + ### Authentication For more details, check the [developers portal](https://developers.rdstation.com/en/authentication). @@ -46,7 +60,7 @@ For more details, check the [developers portal](https://developers.rdstation.com #### Getting authentication URL ```ruby -rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret') +rdstation_authentication = RDStation::Authentication.new redirect_url = 'https://yourapp.org/auth/callback' rdstation_authentication.auth_url(redirect_url) @@ -57,17 +71,34 @@ rdstation_authentication.auth_url(redirect_url) You will need the code param that is returned from RD Station to your application after the user confirms the access at the authorization dialog. ```ruby -rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret') +rdstation_authentication = RDStation::Authentication.new rdstation_authentication.authenticate(code_returned_from_rdstation) +# => { 'access_token' => '54321', 'expires_in' => 86_400, 'refresh_token' => 'refresh' } ``` -#### Updating access_token +#### Updating an expired access_token ```ruby -rdstation_authentication = RDStation::Authentication.new('client_id', 'client_secret') +rdstation_authentication = RDStation::Authentication.new rdstation_authentication.update_access_token('refresh_token') ``` +**NOTE**: This is done automatically when a request fails due to access_token expiration. To keep track of the new token, you have to provide a callback block in configuration. For example: + +```ruby +RDStation.configure do |config| + config.client_id = YOUR_CLIENT_ID + config.client_secret = YOUR_CLIENT_SECRET + config.on_access_token_refresh do |authorization| + # authorization.access_token is the new token + # authorization.refresh_token is the existing refresh_token + # + # If you are using ActiveRecord, you may want to update the stored access_token, like in the following code: + MyStoredAuth.where(refresh_token: authorization.refresh_token).update_all(access_token: authorization.access_token) + end +end +``` + #### Revoking an access_token ```ruby @@ -83,7 +114,7 @@ Note: this will completely remove your credentials from RD Station (`update_acce Returns data about a specific Contact ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.contacts.by_uuid('uuid') ``` @@ -94,7 +125,7 @@ More info: https://developers.rdstation.com/pt-BR/reference/contacts#methodGetDe Returns data about a specific Contact ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.contacts.by_email('email') ``` @@ -109,7 +140,7 @@ contact_info = { name: "Joe Foo" } -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.contacts.update('uuid', contact_info) ``` Contact Default Parameters @@ -139,7 +170,7 @@ contact_info = { identifier = "email" identifier_value = "joe@foo.bar" -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.contacts.upsert(identifier, identifier_value, contact_info) ``` @@ -159,7 +190,7 @@ This creates a new event on RDSM: ```ruby payload = {} # hash representing the payload -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.events.create(payload) ``` @@ -170,7 +201,7 @@ Endpoints to [manage Contact Fields](https://developers.rdstation.com/en/referen #### List all fields ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.fields.all ``` @@ -183,14 +214,14 @@ Choose to receive data based on certain actions, re-cast or marked as an opportu #### List all webhooks ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.webhooks.all ``` #### Getting a webhook by UUID ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.webhooks.by_uuid('WEBHOOK_UUID') ``` @@ -198,7 +229,7 @@ client.webhooks.by_uuid('WEBHOOK_UUID') ```ruby payload = {} # payload representing a webhook -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.webhooks.create(payload) ``` @@ -208,7 +239,7 @@ The required strucutre of the payload is [described here](https://developers.rds ```ruby payload = {} # payload representing a webhook -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.webhooks.create('WEBHOOK_UUID', payload) ``` @@ -217,7 +248,7 @@ The required strucutre of the payload is [described here](https://developers.rds #### Deleting a webhook ```ruby -client = RDStation::Client.new(access_token: 'access_token') +client = RDStation::Client.new(access_token: 'access_token', refresh_token: 'refresh_token') client.webhooks.delete('WEBHOOK_UUID') ``` From c830862b469145249d08a8e9cb229ea7c628c50d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 20:07:09 -0300 Subject: [PATCH 10/15] add deprecation warnings --- CHANGELOG.md | 4 ++-- lib/rdstation/authentication.rb | 5 +++++ lib/rdstation/client.rb | 7 +++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e12dd..08a8465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,9 +35,9 @@ end ### Deprecations -Providing `client_id` and `client_secret` directly to `RDStation::Authentication.new` is deprecated and will be removed in future versions. Use `RDStation.confifure` instead. +Providing `client_id` and `client_secret` directly to `RDStation::Authentication.new` is deprecated and will be removed in future versions. Use `RDStation.configure` instead. -Specifying refresh_token in `RDStation::Client.new(access_token: 'at', refresh_token: 'rt'` is optional right now, but will be mandatory in future versions. +Specifying refresh_token in `RDStation::Client.new(access_token: 'at', refresh_token: 'rt')` is optional right now, but will be mandatory in future versions. ## 2.1.1 diff --git a/lib/rdstation/authentication.rb b/lib/rdstation/authentication.rb index 7c4a50c..4a8e49f 100644 --- a/lib/rdstation/authentication.rb +++ b/lib/rdstation/authentication.rb @@ -8,6 +8,7 @@ class Authentication REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze def initialize(client_id = nil, client_secret = nil) + warn_deprecation if client_id || client_secret @client_id = client_id || RDStation.configuration.client_id @client_secret = client_secret || RDStation.configuration.client_secret end @@ -83,5 +84,9 @@ def post_to_auth_endpoint(params) headers: DEFAULT_HEADERS ) end + + def warn_deprecation + warn "DEPRECATION WARNING: Providing client_id and client_secret directly to RDStation::Authentication.new is deprecated and will be removed in future versions. Use RDStation.configure instead." + end end end diff --git a/lib/rdstation/client.rb b/lib/rdstation/client.rb index 13296e8..7684b7b 100644 --- a/lib/rdstation/client.rb +++ b/lib/rdstation/client.rb @@ -1,6 +1,7 @@ module RDStation class Client def initialize(access_token:, refresh_token: nil) + warn_deprecation unless refresh_token @authorization = Authorization.new( access_token: access_token, refresh_token: refresh_token @@ -22,5 +23,11 @@ def fields def webhooks @webhooks ||= RDStation::Webhooks.new(authorization: @authorization) end + + private + + def warn_deprecation + warn "DEPRECATION WARNING: Specifying refresh_token in RDStation::Client.new(access_token: 'at', refresh_token: 'rt') is optional right now, but will be mandatory in future versions. " + end end end From fe6e41877adb469f9b681bdb119c5880aabe7eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Thu, 12 Dec 2019 20:09:34 -0300 Subject: [PATCH 11/15] bump version --- lib/rdstation/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rdstation/version.rb b/lib/rdstation/version.rb index ac8b43f..55df37d 100644 --- a/lib/rdstation/version.rb +++ b/lib/rdstation/version.rb @@ -1,3 +1,3 @@ module RDStation - VERSION = '2.1.1'.freeze + VERSION = '2.2.0'.freeze end From 7661c6e481514264d4e89ea09d5a1e4f2f44ab3d Mon Sep 17 00:00:00 2001 From: Rafael Possatto Date: Thu, 12 Dec 2019 22:04:44 -0300 Subject: [PATCH 12/15] creates tests of retryable for events, fields and webhooks --- spec/lib/rdstation/events_spec.rb | 5 +++++ spec/lib/rdstation/fields_spec.rb | 5 +++++ spec/lib/rdstation/webhooks_spec.rb | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/spec/lib/rdstation/events_spec.rb b/spec/lib/rdstation/events_spec.rb index c2088e4..3f6a0a8 100644 --- a/spec/lib/rdstation/events_spec.rb +++ b/spec/lib/rdstation/events_spec.rb @@ -108,6 +108,11 @@ } end + it 'calls retryable_request' do + expect(event_with_valid_token).to receive(:retryable_request) + event_with_valid_token.create({}) + end + context 'with a valid auth token' do before do stub_request(:post, events_endpoint) diff --git a/spec/lib/rdstation/fields_spec.rb b/spec/lib/rdstation/fields_spec.rb index 4f60c34..511110a 100644 --- a/spec/lib/rdstation/fields_spec.rb +++ b/spec/lib/rdstation/fields_spec.rb @@ -38,6 +38,11 @@ } end + it 'calls retryable_request' do + expect(rdstation_fields_with_valid_token).to receive(:retryable_request) + rdstation_fields_with_valid_token.all + end + context 'with a valid auth token' do before do stub_request(:get, fields_endpoint) diff --git a/spec/lib/rdstation/webhooks_spec.rb b/spec/lib/rdstation/webhooks_spec.rb index 35787e1..9b69ea8 100644 --- a/spec/lib/rdstation/webhooks_spec.rb +++ b/spec/lib/rdstation/webhooks_spec.rb @@ -23,6 +23,11 @@ end describe '#all' do + it 'calls retryable_request' do + expect(webhooks_client).to receive(:retryable_request) + webhooks_client.all + end + context 'when the request is successful' do let(:webhooks) do { @@ -77,6 +82,11 @@ let(:uuid) { '5408c5a3-4711-4f2e-8d0b-13407a3e30f3' } let(:webhooks_endpoint_by_uuid) { webhooks_endpoint + uuid } + it 'calls retryable_request' do + expect(webhooks_client).to receive(:retryable_request) + webhooks_client.by_uuid('uuid') + end + context 'when the request is successful' do let(:webhook) do { @@ -126,6 +136,11 @@ } end + it 'calls retryable_request' do + expect(webhooks_client).to receive(:retryable_request) + webhooks_client.create('payload') + end + context 'when the request is successful' do let(:webhook) do { @@ -177,6 +192,11 @@ } end + it 'calls retryable_request' do + expect(webhooks_client).to receive(:retryable_request) + webhooks_client.update('uuid', 'payload') + end + context 'when the request is successful' do let(:updated_webhook) do { @@ -219,6 +239,11 @@ let(:uuid) { '5408c5a3-4711-4f2e-8d0b-13407a3e30f3' } let(:webhooks_endpoint_by_uuid) { webhooks_endpoint + uuid } + it 'calls retryable_request' do + expect(webhooks_client).to receive(:retryable_request) + webhooks_client.delete('uuid') + end + context 'when the request is successful' do before do stub_request(:delete, webhooks_endpoint_by_uuid).with(headers: headers).to_return(status: 204) From b524e8916e5bfb97cbf3cbfe82791e2ebcd2dced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Fri, 13 Dec 2019 10:53:55 -0300 Subject: [PATCH 13/15] add expires_in to authorization --- lib/rdstation/authorization.rb | 5 +++-- lib/rdstation/retryable_request.rb | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/rdstation/authorization.rb b/lib/rdstation/authorization.rb index 7eb6bea..0e80e39 100644 --- a/lib/rdstation/authorization.rb +++ b/lib/rdstation/authorization.rb @@ -1,10 +1,11 @@ module RDStation class Authorization attr_reader :refresh_token - attr_accessor :access_token - def initialize(access_token:, refresh_token: nil) + attr_accessor :access_token, :access_token_expires_in + def initialize(access_token:, refresh_token: nil, access_token_expires_in: nil) @access_token = access_token @refresh_token = refresh_token + @access_token_expires_in = access_token_expires_in validate_access_token access_token end diff --git a/lib/rdstation/retryable_request.rb b/lib/rdstation/retryable_request.rb index 1a13884..650fbf1 100644 --- a/lib/rdstation/retryable_request.rb +++ b/lib/rdstation/retryable_request.rb @@ -28,6 +28,7 @@ def refresh_access_token(authorization) client = RDStation::Authentication.new response = client.update_access_token(authorization.refresh_token) authorization.access_token = response['access_token'] + authorization.access_token_expires_in = response['expires_in'] RDStation.configuration&.access_token_refresh_callback&.call(authorization) end end From 7164fda0395ba9622bb2ec9f9fd0c27630a87cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Fri, 13 Dec 2019 10:56:21 -0300 Subject: [PATCH 14/15] fix docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7df40fc..55f746f 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ RDStation.configure do |config| config.client_id = YOUR_CLIENT_ID config.client_secret = YOUR_CLIENT_SECRET config.on_access_token_refresh do |authorization| + # authorization.access_token_expires_in is the time (in seconds for with the token is valid) # authorization.access_token is the new token # authorization.refresh_token is the existing refresh_token # From 40cb1f3aa939fd9b79ce3319738d61ae8d44625e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Hornburg?= Date: Fri, 13 Dec 2019 11:18:18 -0300 Subject: [PATCH 15/15] do not explode when config is empty --- lib/rdstation/authentication.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rdstation/authentication.rb b/lib/rdstation/authentication.rb index 4a8e49f..31fefca 100644 --- a/lib/rdstation/authentication.rb +++ b/lib/rdstation/authentication.rb @@ -9,8 +9,8 @@ class Authentication def initialize(client_id = nil, client_secret = nil) warn_deprecation if client_id || client_secret - @client_id = client_id || RDStation.configuration.client_id - @client_secret = client_secret || RDStation.configuration.client_secret + @client_id = client_id || RDStation.configuration&.client_id + @client_secret = client_secret || RDStation.configuration&.client_secret end #