diff --git a/Gemfile.lock b/Gemfile.lock index 78e7649e..b53a0ab5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - xero-ruby (2.9.0) + xero-ruby (2.9.1) faraday (~> 1.0, >= 1.0.1) json (~> 2.1, >= 2.1.0) @@ -12,11 +12,15 @@ GEM byebug (11.1.3) coderay (1.1.3) diff-lcs (1.4.4) - faraday (1.3.0) + faraday (1.4.1) + faraday-excon (~> 1.1) faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) multipart-post (>= 1.2, < 3) - ruby2_keywords + ruby2_keywords (>= 0.0.4) + faraday-excon (1.1.0) faraday-net_http (1.0.1) + faraday-net_http_persistent (1.1.0) jaro_winkler (1.5.4) json (2.5.1) method_source (1.0.0) diff --git a/docs/accounting/index.html b/docs/accounting/index.html index 75a6f4ed..a0c7533e 100644 --- a/docs/accounting/index.html +++ b/docs/accounting/index.html @@ -5795,7 +5795,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/assets/index.html b/docs/assets/index.html index 0edddd7c..6c223e2e 100644 --- a/docs/assets/index.html +++ b/docs/assets/index.html @@ -1365,7 +1365,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/files/index.html b/docs/files/index.html index a4f14598..555e26a9 100644 --- a/docs/files/index.html +++ b/docs/files/index.html @@ -1129,7 +1129,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/payroll_au/DeductionLine.md b/docs/payroll_au/DeductionLine.md index 01f60081..d2e7c8f0 100644 --- a/docs/payroll_au/DeductionLine.md +++ b/docs/payroll_au/DeductionLine.md @@ -5,7 +5,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **deduction_type_id** | **String** | Xero deduction type identifier | -**calculation_type** | [**DeductionTypeCalculationType**](DeductionTypeCalculationType.md) | | +**calculation_type** | [**DeductionTypeCalculationType**](DeductionTypeCalculationType.md) | | [optional] **amount** | **BigDecimal** | Deduction type amount | [optional] **percentage** | **BigDecimal** | The Percentage of the Deduction | [optional] **number_of_units** | **BigDecimal** | Deduction number of units | [optional] diff --git a/docs/payroll_au/index.html b/docs/payroll_au/index.html index 3bfbcc35..1664703f 100644 --- a/docs/payroll_au/index.html +++ b/docs/payroll_au/index.html @@ -970,7 +970,7 @@ }; defs["DeductionLine"] = { "title" : "", - "required" : [ "CalculationType", "DeductionTypeID" ], + "required" : [ "DeductionTypeID" ], "type" : "object", "properties" : { "DeductionTypeID" : { @@ -3207,7 +3207,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/payroll_nz/index.html b/docs/payroll_nz/index.html index de729b5f..f4347a68 100644 --- a/docs/payroll_nz/index.html +++ b/docs/payroll_nz/index.html @@ -3812,7 +3812,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/payroll_uk/index.html b/docs/payroll_uk/index.html index 9655516d..8edeb2ae 100644 --- a/docs/payroll_uk/index.html +++ b/docs/payroll_uk/index.html @@ -3480,7 +3480,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/docs/projects/index.html b/docs/projects/index.html index 5a4c7785..884b7877 100644 --- a/docs/projects/index.html +++ b/docs/projects/index.html @@ -1442,7 +1442,7 @@ } else if (location.includes('php')){ sdkLang = 'php' sdkName = 'xero-php-oauth2' - } else if (location.includes('dotnet')){ + } else if (location.includes('xero-netstandard') || location.includes('csharp')){ sdkLang = 'dotnet' sdkName = 'Xero-NetStandard' } else if (location.includes('java')){ diff --git a/lib/xero-ruby/api_client.rb b/lib/xero-ruby/api_client.rb index a80285ce..260c432c 100644 --- a/lib/xero-ruby/api_client.rb +++ b/lib/xero-ruby/api_client.rb @@ -38,7 +38,8 @@ def initialize(config: {}, credentials: {}) @redirect_uri = credentials[:redirect_uri] @scopes = credentials[:scopes] @state = credentials[:state] - @config = append_to_default_config(config) + default_config = Configuration.default.clone + @config = append_to_default_config(default_config, config) @user_agent = "xero-ruby-#{VERSION}" @default_headers = { @@ -47,9 +48,9 @@ def initialize(config: {}, credentials: {}) } end - def append_to_default_config(user_config) - config = Configuration.default - user_config.each{|k,v| config.send("#{k}=", v) } if user_config + def append_to_default_config(default_config, user_config) + config = default_config + user_config.each{|k,v| config.send("#{k}=", v)} config end @@ -96,22 +97,30 @@ def payroll_uk_api # Token Helpers def token_set - XeroRuby.configure.token_set + @config.token_set end def access_token - XeroRuby.configure.access_token + @config.access_token + end + + def id_token + @config.id_token end def set_token_set(token_set) # helper to set the token_set on a client once the user # has a valid token set ( access_token & refresh_token ) - XeroRuby.configure.token_set = token_set + @config.token_set = token_set set_access_token(token_set['access_token']) end def set_access_token(access_token) - XeroRuby.configure.access_token = access_token + @config.access_token = access_token + end + + def set_id_token(id_token) + @config.id_token = id_token end def get_token_set_from_callback(params) @@ -182,7 +191,26 @@ def call_api(http_method, path, api_client, opts = {}) :client_key => @config.ssl_client_key } - connection = Faraday.new(:url => config.base_url, :ssl => ssl_options) do |conn| + case api_client + when "AccountingApi" + method_base_url = @config.accounting_url + when "AssetApi" + method_base_url = @config.asset_url + when "FilesApi" + method_base_url = @config.files_url + when "PayrollAuApi" + method_base_url = @config.payroll_au_url + when "PayrollNzApi" + method_base_url = @config.payroll_nz_url + when "PayrollUkApi" + method_base_url = @config.payroll_uk_url + when "ProjectApi" + method_base_url = @config.project_url + else + method_base_url = @config.accounting_url + end + + connection = Faraday.new(:url => method_base_url, :ssl => ssl_options) do |conn| conn.basic_auth(config.username, config.password) if opts[:header_params]["Content-Type"] == "multipart/form-data" conn.request :multipart @@ -265,7 +293,8 @@ def build_request(http_method, path, request, opts = {}) end end request.headers = header_params - request.options.timeout = @config.timeout + timeout = @config.timeout + request.options.timeout = timeout if timeout > 0 request.body = req_body request.url url request.params = query_params diff --git a/lib/xero-ruby/configuration.rb b/lib/xero-ruby/configuration.rb index ebf4163c..78dbc44a 100644 --- a/lib/xero-ruby/configuration.rb +++ b/lib/xero-ruby/configuration.rb @@ -60,8 +60,11 @@ class Configuration # @return [String] attr_accessor :password - # Defines the access token (Bearer) used with OAuth2. + # Defines the access token (Bearer) used with OAuth2 authorization attr_accessor :access_token + + # Defines OpenID Connect id_token containing Xero user authentication detail + attr_accessor :id_token # Defines the token set used with OAuth2. May include id/access/refresh token & other meta info. attr_accessor :token_set @@ -146,6 +149,8 @@ def initialize @payroll_au_url = 'https://api.xero.com/payroll.xro/1.0/' @payroll_nz_url = 'https://api.xero.com/payroll.xro/2.0/' @payroll_uk_url = 'https://api.xero.com/payroll.xro/2.0/' + @access_token = nil + @id_token = nil @api_key = {} @api_key_prefix = {} @timeout = 0 @@ -191,6 +196,14 @@ def base_path=(base_path) def base_url=(api_url) @base_url = api_url end + + def access_token=(access_token) + @access_token = access_token + end + + def id_token=(id_token) + @id_token = id_token + end # Gets API key (with prefix if set). # @param [String] param_name the parameter name of API key auth diff --git a/lib/xero-ruby/models/payroll_au/deduction_line.rb b/lib/xero-ruby/models/payroll_au/deduction_line.rb index 5f7b8c73..11f77d5d 100644 --- a/lib/xero-ruby/models/payroll_au/deduction_line.rb +++ b/lib/xero-ruby/models/payroll_au/deduction_line.rb @@ -97,10 +97,6 @@ def list_invalid_properties invalid_properties.push('invalid value for "deduction_type_id", deduction_type_id cannot be nil.') end - if @calculation_type.nil? - invalid_properties.push('invalid value for "calculation_type", calculation_type cannot be nil.') - end - invalid_properties end @@ -108,7 +104,6 @@ def list_invalid_properties # @return true if the model is valid def valid? return false if @deduction_type_id.nil? - return false if @calculation_type.nil? true end diff --git a/lib/xero-ruby/version.rb b/lib/xero-ruby/version.rb index 4a2d3681..1a013880 100644 --- a/lib/xero-ruby/version.rb +++ b/lib/xero-ruby/version.rb @@ -7,9 +7,9 @@ Generated by: https://openapi-generator.tech OpenAPI Generator version: 4.3.1 -The version of the XeroOpenAPI document: 2.10.4 +The version of the XeroOpenAPI document: 2.10.5 =end module XeroRuby - VERSION = '2.9.0' + VERSION = '2.9.1' end diff --git a/spec/api_client_spec.rb b/spec/api_client_spec.rb index 56a7c260..a38481b2 100644 --- a/spec/api_client_spec.rb +++ b/spec/api_client_spec.rb @@ -371,4 +371,85 @@ expect(api_client.sanitize_filename('.\sun.gif')).to eq('sun.gif') end end + + describe 'thread safety in the XeroClient' do + let(:creds) {{ + client_id: 'abc', + client_secret: '123', + redirect_uri: 'https://mydomain.com/callback', + scopes: 'openid profile email accounting.transactions' + }} + let(:api_client_1) {XeroRuby::ApiClient.new(credentials: creds)} + let(:api_client_2) {XeroRuby::ApiClient.new(credentials: creds)} + let(:api_client_3) {XeroRuby::ApiClient.new(credentials: creds)} + + let(:tkn_set_1){{id_token: "abc.123.1", access_token: "xxx.yyy.zzz.111"}} + let(:tkn_set_2){{id_token: "efg.456.2", access_token: "xxx.yyy.zzz.222"}} + + describe 'when configuration is changed, other instantiations of the client are not affected' do + it 'applies to #set_access_token' do + expect(api_client_1.access_token).to eq(nil) + expect(api_client_2.access_token).to eq(nil) + expect(api_client_3.access_token).to eq(nil) + + api_client_1.set_access_token("ACCESS_TOKEN_1") + expect(api_client_1.access_token).to eq("ACCESS_TOKEN_1") + expect(api_client_2.access_token).to eq(nil) + expect(api_client_3.access_token).to eq(nil) + + api_client_2.set_access_token("ACCESS_TOKEN_2") + expect(api_client_1.access_token).to eq("ACCESS_TOKEN_1") + expect(api_client_2.access_token).to eq("ACCESS_TOKEN_2") + expect(api_client_3.access_token).to eq(nil) + + api_client_3.set_access_token("ACCESS_TOKEN_3") + expect(api_client_1.access_token).to eq("ACCESS_TOKEN_1") + expect(api_client_2.access_token).to eq("ACCESS_TOKEN_2") + expect(api_client_3.access_token).to eq("ACCESS_TOKEN_3") + end + + it 'applies to #set_id_token' do + expect(api_client_1.id_token).to eq(nil) + expect(api_client_2.id_token).to eq(nil) + + api_client_1.set_id_token("id_token_1") + expect(api_client_1.id_token).to eq("id_token_1") + expect(api_client_2.id_token).to eq(nil) + + api_client_2.set_id_token("id_token_2") + expect(api_client_1.id_token).to eq("id_token_1") + expect(api_client_2.id_token).to eq("id_token_2") + end + + it 'applies to #set_token_set' do + expect(api_client_1.token_set).to eq(nil) + expect(api_client_2.token_set).to eq(nil) + + api_client_1.set_token_set(tkn_set_1) + expect(api_client_1.token_set).to eq(tkn_set_1) + expect(api_client_2.token_set).to eq(nil) + + api_client_2.set_token_set(tkn_set_2) + expect(api_client_1.token_set).to eq(tkn_set_1) + expect(api_client_2.token_set).to eq(tkn_set_2) + end + + it 'applies to #base_url' do + expect(api_client_1.config.base_url).to eq(nil) + expect(api_client_2.config.base_url).to eq(nil) + + api_client_1.accounting_api + expect(api_client_1.config.base_url).to eq(api_client_1.config.accounting_url) + expect(api_client_2.config.base_url).to eq(nil) + + api_client_2.files_api + expect(api_client_1.config.base_url).to eq(api_client_1.config.accounting_url) + expect(api_client_2.config.base_url).to eq(api_client_1.config.files_url) + + api_client_2.project_api + expect(api_client_1.config.base_url).to eq(api_client_1.config.accounting_url) + expect(api_client_2.config.base_url).to eq(api_client_1.config.project_url) + end + end + end end