my_john_deere_api 2.5.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/my_john_deere_api.rb +1 -1
  3. data/lib/my_john_deere_api/authorize.rb +36 -27
  4. data/lib/my_john_deere_api/client.rb +38 -29
  5. data/lib/my_john_deere_api/consumer.rb +46 -35
  6. data/lib/my_john_deere_api/helpers/case_conversion.rb +5 -2
  7. data/lib/my_john_deere_api/helpers/uri_helpers.rb +1 -1
  8. data/lib/my_john_deere_api/model/contribution_definition.rb +1 -1
  9. data/lib/my_john_deere_api/net_http_retry/decorator.rb +10 -11
  10. data/lib/my_john_deere_api/net_http_retry/invalid_response_error.rb +2 -2
  11. data/lib/my_john_deere_api/request/collection/asset_locations.rb +1 -1
  12. data/lib/my_john_deere_api/request/collection/assets.rb +1 -1
  13. data/lib/my_john_deere_api/request/collection/base.rb +2 -14
  14. data/lib/my_john_deere_api/request/collection/contribution_definitions.rb +1 -1
  15. data/lib/my_john_deere_api/request/collection/contribution_products.rb +1 -1
  16. data/lib/my_john_deere_api/request/collection/fields.rb +1 -1
  17. data/lib/my_john_deere_api/request/collection/flags.rb +1 -1
  18. data/lib/my_john_deere_api/request/collection/organizations.rb +1 -1
  19. data/lib/my_john_deere_api/request/create/asset.rb +8 -11
  20. data/lib/my_john_deere_api/request/create/asset_location.rb +24 -25
  21. data/lib/my_john_deere_api/request/create/base.rb +2 -20
  22. data/lib/my_john_deere_api/request/individual/asset.rb +2 -2
  23. data/lib/my_john_deere_api/request/individual/base.rb +6 -24
  24. data/lib/my_john_deere_api/request/individual/contribution_definition.rb +1 -1
  25. data/lib/my_john_deere_api/request/individual/contribution_product.rb +1 -1
  26. data/lib/my_john_deere_api/request/individual/field.rb +1 -1
  27. data/lib/my_john_deere_api/request/individual/organization.rb +1 -1
  28. data/lib/my_john_deere_api/request/update/asset.rb +2 -2
  29. data/lib/my_john_deere_api/request/update/base.rb +1 -19
  30. data/lib/my_john_deere_api/version.rb +1 -1
  31. data/test/lib/my_john_deere_api/authorize_test.rb +37 -25
  32. data/test/lib/my_john_deere_api/client_test.rb +22 -56
  33. data/test/lib/my_john_deere_api/consumer_test.rb +16 -28
  34. data/test/lib/my_john_deere_api/helpers/uri_helpers_test.rb +0 -10
  35. data/test/lib/my_john_deere_api/model/asset_location_test.rb +0 -4
  36. data/test/lib/my_john_deere_api/model/asset_test.rb +9 -8
  37. data/test/lib/my_john_deere_api/model/base_test.rb +4 -8
  38. data/test/lib/my_john_deere_api/model/contribution_definition_test.rb +3 -7
  39. data/test/lib/my_john_deere_api/model/contribution_product_test.rb +4 -7
  40. data/test/lib/my_john_deere_api/model/field_test.rb +4 -6
  41. data/test/lib/my_john_deere_api/model/flag_test.rb +6 -7
  42. data/test/lib/my_john_deere_api/model/organization_test.rb +3 -5
  43. data/test/lib/my_john_deere_api/net_http_retry/decorator_test.rb +14 -14
  44. data/test/lib/my_john_deere_api/net_http_retry/invalid_response_error_test.rb +22 -2
  45. data/test/lib/my_john_deere_api/request/collection/asset_locations_test.rb +2 -2
  46. data/test/lib/my_john_deere_api/request/collection/assets_test.rb +2 -2
  47. data/test/lib/my_john_deere_api/request/collection/contribution_definitions_test.rb +2 -2
  48. data/test/lib/my_john_deere_api/request/collection/contribution_products_test.rb +2 -2
  49. data/test/lib/my_john_deere_api/request/collection/fields_test.rb +2 -2
  50. data/test/lib/my_john_deere_api/request/collection/flags_test.rb +2 -2
  51. data/test/lib/my_john_deere_api/request/collection/organizations_test.rb +2 -2
  52. data/test/lib/my_john_deere_api/request/create/asset_location_test.rb +3 -2
  53. data/test/lib/my_john_deere_api/request/create/asset_test.rb +5 -4
  54. data/test/lib/my_john_deere_api/request/create/base_test.rb +0 -14
  55. data/test/lib/my_john_deere_api/request/individual/asset_test.rb +2 -3
  56. data/test/lib/my_john_deere_api/request/individual/base_test.rb +0 -1
  57. data/test/lib/my_john_deere_api/request/individual/contribution_definition_test.rb +2 -3
  58. data/test/lib/my_john_deere_api/request/individual/contribution_product_test.rb +2 -3
  59. data/test/lib/my_john_deere_api/request/individual/field_test.rb +2 -3
  60. data/test/lib/my_john_deere_api/request/individual/organization_test.rb +2 -3
  61. data/test/lib/my_john_deere_api/request/update/asset_test.rb +5 -17
  62. data/test/lib/my_john_deere_api/request/update/base_test.rb +0 -14
  63. data/test/support/helper.rb +14 -5
  64. data/test/support/link_helpers.rb +14 -0
  65. data/test/support/response_helpers.rb +18 -0
  66. data/test/support/vcr/catalog.yml +44 -37
  67. data/test/support/vcr/get_access_token.yml +90 -17
  68. data/test/support/vcr/get_refresh_token.yml +159 -0
  69. data/test/support/vcr/get_request_url.yml +51 -0
  70. data/test/support/vcr_setup.rb +80 -19
  71. metadata +11 -8
  72. data/test/support/vcr/get_request_token.yml +0 -83
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba69ef151796a688f69eba0f6a396d277baa893aa9e589922f2f2eac7b4e3a6a
4
- data.tar.gz: e0539d88ae84a348113be06e9f81299b64a5c74598428e9d2a33ec00b0b49584
3
+ metadata.gz: b36f05ba98236dbdddfcab7086fa13c6af8d673dc8c2d2c1f185abcbf0f1ceeb
4
+ data.tar.gz: d5bda4fae253a293fd58b44045eacf2b7eba8dd142560aa3813da3deb334bc7c
5
5
  SHA512:
6
- metadata.gz: 9a40c8720b6e44da21bb5c2d0247055cf8b4f7d04e5b6d9e5d6db03778af368e64b5d2ba375aa02aa671aebdbf3d64d87c8090beb958bb7dc83ef5b1da52196e
7
- data.tar.gz: 77bac482b0518e59b534d1916b9155c2a59e75308cc71e93354c9a3d071fa10c35362ee836f3eb456b15f612b73d503f06c735d7af942d220415a39c6943c606
6
+ metadata.gz: 454ac16787a9448ce1fb0d5db1bc4f2b39b9c0c729552fe9c0730093d681dc442fc33b21cdbb8aea79678a1a2e1fd1bdab2ff042b931251818435cbcbd7a54e9
7
+ data.tar.gz: b90f4aa488daa2e0c99c31ede5045769f6d7cd04fce875d92375b369318845737fc5dbf9d9e4824b048980261e6ccc1880e8b4803a01cd9f0be4299d826cd491
@@ -1,4 +1,4 @@
1
- require 'oauth'
1
+ require 'oauth2'
2
2
  require 'uri'
3
3
  require 'json'
4
4
 
@@ -2,10 +2,7 @@ module MyJohnDeereApi
2
2
  class Authorize
3
3
  include Helpers::EnvironmentHelper
4
4
 
5
- attr_reader :api_key, :api_secret,
6
- :request_token, :request_secret,
7
- :access_token, :access_secret,
8
- :environment, :options
5
+ attr_reader :api_key, :api_secret, :environment, :options, :token_hash
9
6
 
10
7
  DEFAULTS = {
11
8
  environment: :live
@@ -23,6 +20,9 @@ module MyJohnDeereApi
23
20
  @api_key = api_key
24
21
  @api_secret = api_secret
25
22
  self.environment = @options[:environment]
23
+
24
+ # This is only set upon verification
25
+ @token_hash = nil
26
26
  end
27
27
 
28
28
  ##
@@ -32,38 +32,47 @@ module MyJohnDeereApi
32
32
  def authorize_url
33
33
  return @authorize_url if defined?(@authorize_url)
34
34
 
35
- request_options = options.slice(:oauth_callback)
35
+ request_options = options.slice(:redirect_uri, :state, :scope)
36
+
37
+ if options.key?(:scopes)
38
+ options[:scopes] << 'offline_access' unless options[:scopes].include?('offline_access')
39
+ request_options[:scope] = options[:scopes].join(' ')
40
+ end
36
41
 
37
- requester = consumer.get_request_token(request_options)
38
- @request_token = requester.token
39
- @request_secret = requester.secret
42
+ # generate a default unique-ish "state" key if not provided
43
+ unless request_options.key?(:state)
44
+ request_options[:state] = (rand(8000) + 1000).to_s
45
+ end
40
46
 
41
- @authorize_url = requester.authorize_url(request_options)
47
+ @authorize_url = oauth_client.auth_code.authorize_url(request_options)
42
48
  end
43
49
 
44
50
  ##
45
- # API consumer that makes non-user-specific GET requests
51
+ # API client that makes authentication requests
46
52
 
47
- def consumer
48
- return @consumer if defined?(@consumer)
49
- @consumer = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment).app_get
53
+ def oauth_client
54
+ return @oauth_client if defined?(@oauth_client)
55
+ @oauth_client = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment).auth_client
50
56
  end
51
57
 
52
58
  ##
53
- # Turn a verification code into access tokens. If this is
54
- # run from a separate process than the one that created
55
- # the initial RequestToken, the request token/secret
56
- # can be passed in.
57
-
58
- def verify(code, token=nil, secret=nil)
59
- token ||= request_token
60
- secret ||= request_secret
61
-
62
- requester = OAuth::RequestToken.new(consumer, token, secret)
63
- access_object = requester.get_access_token(oauth_verifier: code)
64
- @access_token = access_object.token
65
- @access_secret = access_object.secret
66
- nil
59
+ # Turn a verification code into access token.
60
+
61
+ def verify(code)
62
+ token = oauth_client.auth_code.get_token(code, redirect_uri: options[:redirect_uri])
63
+
64
+ # normalize hash
65
+ @token_hash = JSON.parse(token.to_hash.to_json)
66
+ end
67
+
68
+ ##
69
+ # Use an old token hash to generate a new token hash.
70
+
71
+ def refresh_from_hash(old_token_hash)
72
+ old_token = OAuth2::AccessToken.from_hash(oauth_client, old_token_hash)
73
+ new_token = old_token.refresh!
74
+
75
+ new_token.to_hash
67
76
  end
68
77
  end
69
78
  end
@@ -4,7 +4,7 @@ module MyJohnDeereApi
4
4
  include Helpers::CaseConversion
5
5
 
6
6
  attr_accessor :contribution_definition_id
7
- attr_reader :api_key, :api_secret, :access_token, :access_secret, :http_retry_options
7
+ attr_reader :api_key, :api_secret, :token_hash, :http_retry_options
8
8
 
9
9
  DEFAULTS = {
10
10
  environment: :live,
@@ -13,18 +13,17 @@ module MyJohnDeereApi
13
13
 
14
14
  ##
15
15
  # Creates the client with everything it needs to perform API requests.
16
- # User-specific credentials are optional, but user-specific API
17
- # requests are only possible if they are supplied.
16
+ # User-specific token_hash is optional, but user-specific API
17
+ # requests are only possible if it is supplied.
18
18
  #
19
19
  # options:
20
20
  #
21
21
  # [:environment] :sandbox or :live
22
22
  #
23
23
  # [:contribution_definition_id] optional, but needed for some requests
24
- # like asset create/update.
24
+ # like asset create/update
25
25
  #
26
- # [:access] an array with two elements, the access_token
27
- # and the access_secret of the given user
26
+ # [:token_hash] a hash used to re-create the access token
28
27
 
29
28
  def initialize(api_key, api_secret, options = {})
30
29
  options = DEFAULTS.merge(options)
@@ -32,8 +31,8 @@ module MyJohnDeereApi
32
31
  @api_key = api_key
33
32
  @api_secret = api_secret
34
33
 
35
- if options.has_key?(:access) && options[:access].is_a?(Array)
36
- @access_token, @access_secret = options[:access]
34
+ if options.has_key?(:token_hash) && options[:token_hash].is_a?(Hash)
35
+ @token_hash = options[:token_hash]
37
36
  end
38
37
 
39
38
  self.environment = options[:environment]
@@ -49,22 +48,38 @@ module MyJohnDeereApi
49
48
  return @accessor if defined?(@accessor)
50
49
 
51
50
  @accessor = NetHttpRetry::Decorator.new(
52
- OAuth::AccessToken.new(
53
- consumer.user_get,
54
- access_token,
55
- access_secret
56
- ),
51
+ OAuth2::AccessToken.from_hash(oauth_client, token_hash),
57
52
  http_retry_options
58
53
  )
59
54
  end
60
55
 
56
+ ##
57
+ # Returns the URI for the Contribution Definiton ID, if provided
58
+
59
+ def contribution_definition_uri
60
+ return @contribution_definition_uri if defined?(@contribution_definition_uri)
61
+
62
+ @contribution_definition_uri =
63
+ if contribution_definition_id
64
+ "#{site}/contributionDefinitions/#{contribution_definition_id}"
65
+ else
66
+ nil
67
+ end
68
+ end
69
+
70
+ ##
71
+ # Returns the base url for requests
72
+
73
+ def site
74
+ return @site if defined?(@site)
75
+ @site = accessor.client.site
76
+ end
77
+
61
78
  ##
62
79
  # generic user-specific GET request method that returns JSON
63
80
 
64
81
  def get resource
65
- resource = resource.to_s
66
- resource = "/#{resource}" unless resource =~ /^\//
67
- response = accessor.get(resource, headers)
82
+ response = accessor.get(resource, headers: headers)
68
83
 
69
84
  JSON.parse(response.body)
70
85
  end
@@ -73,9 +88,7 @@ module MyJohnDeereApi
73
88
  # generic user-specific POST request method that returns JSON or response
74
89
 
75
90
  def post resource, body
76
- resource = resource.to_s
77
- resource = "/#{resource}" unless resource =~ /^\//
78
- response = accessor.post(resource, camelize(body).to_json, post_headers)
91
+ response = accessor.post(resource, body: camelize(body).to_json, headers: post_headers)
79
92
 
80
93
  if response.body && response.body.size > 0
81
94
  JSON.parse(response.body)
@@ -88,9 +101,7 @@ module MyJohnDeereApi
88
101
  # generic user-specific PUT request method that returns JSON or response
89
102
 
90
103
  def put resource, body
91
- resource = resource.to_s
92
- resource = "/#{resource}" unless resource =~ /^\//
93
- response = accessor.put(resource, camelize(body).to_json, post_headers)
104
+ response = accessor.put(resource, body: camelize(body).to_json, headers: post_headers)
94
105
 
95
106
  if response.body && response.body.size > 0
96
107
  JSON.parse(response.body)
@@ -103,9 +114,7 @@ module MyJohnDeereApi
103
114
  # generic user-specific DELETE request method that returns JSON or response
104
115
 
105
116
  def delete resource
106
- resource = resource.to_s
107
- resource = "/#{resource}" unless resource =~ /^\//
108
- response = accessor.delete(resource, headers)
117
+ response = accessor.delete(resource, headers: headers)
109
118
 
110
119
  if response.body && response.body.size > 0
111
120
  JSON.parse(response.body)
@@ -133,11 +142,11 @@ module MyJohnDeereApi
133
142
  private
134
143
 
135
144
  ##
136
- # Returns an oAuth consumer which can be used to build requests
145
+ # Returns an oAuth client which can be used to build requests
137
146
 
138
- def consumer
139
- return @consumer if defined?(@consumer)
140
- @consumer = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment)
147
+ def oauth_client
148
+ return @oauth_client if defined?(@oauth_client)
149
+ @oauth_client = MyJohnDeereApi::Consumer.new(@api_key, @api_secret, environment: environment).platform_client
141
150
  end
142
151
 
143
152
  def headers
@@ -3,12 +3,12 @@ module MyJohnDeereApi
3
3
  include Helpers::CaseConversion
4
4
  include Helpers::EnvironmentHelper
5
5
 
6
- attr_reader :api_key, :api_secret, :environment, :base_url
6
+ attr_reader :api_key, :api_secret, :environment, :site
7
7
 
8
8
  # valid API urls
9
9
  URLS = {
10
10
  sandbox: 'https://sandboxapi.deere.com',
11
- live: 'https://api.soa-proxy.deere.com',
11
+ live: 'https://partnerapi.deere.com',
12
12
  }
13
13
 
14
14
  DEFAULTS = {
@@ -22,65 +22,76 @@ module MyJohnDeereApi
22
22
  @api_secret = api_secret
23
23
 
24
24
  self.environment = options[:environment]
25
- @base_url = options[:base_url] || URLS[@environment]
25
+ @site = options[:site] || URLS[@environment]
26
26
  end
27
27
 
28
28
  ##
29
- # oAuth Consumer which uses just the base url, for
30
- # app-wide, non user-specific GET requests.
29
+ # oAuth client for platform requests
31
30
 
32
- def app_get
33
- @app_get ||= consumer(base_url)
31
+ def platform_client
32
+ return @platform_client if defined?(@platform_client)
33
+
34
+ @platform_client = OAuth2::Client.new(
35
+ api_key,
36
+ api_secret,
37
+ site: site,
38
+ headers: headers,
39
+ raise_errors: false,
40
+ )
34
41
  end
35
42
 
36
43
  ##
37
- # oAuth Consumer which uses the proper url for user-specific GET requests.
44
+ # oAuth client for user authentication
38
45
 
39
- def user_get
40
- @user_get ||= consumer("#{base_url}/platform")
41
- end
46
+ def auth_client
47
+ return @auth_client if defined?(@auth_client)
42
48
 
43
- private
49
+ # We build this without the `client` method because the authorization links
50
+ # require an extra API call to JD that is only needed for authorization.
44
51
 
45
- def consumer(site)
46
- OAuth::Consumer.new(
52
+ @auth_client = OAuth2::Client.new(
47
53
  api_key,
48
54
  api_secret,
49
55
  site: site,
50
- header: header,
51
- http_method: :get,
52
- request_token_url: links[:request_token],
53
- access_token_url: links[:access_token],
54
- authorize_url: links[:authorize_request_token]
56
+ authorize_url: authorization_links[:authorization],
57
+ token_url: authorization_links[:token],
58
+ raise_errors: false,
55
59
  )
56
60
  end
57
61
 
58
- def links
59
- return @links if defined?(@links)
62
+ private
63
+
64
+ def authorization
65
+ return @authorization if defined?(@authorization)
60
66
 
61
- catalog = OAuth::Consumer.new(api_key, api_secret)
67
+ json = OAuth2::Client.new(api_key, api_secret)
62
68
  .request(
63
69
  :get,
64
- "#{base_url}/platform/",
65
- nil,
66
- {},
67
- header
70
+ 'https://signin.johndeere.com/oauth2/aus78tnlaysMraFhC1t7/.well-known/oauth-authorization-server',
71
+ headers: headers,
72
+ raise_errors: false,
68
73
  ).body
69
74
 
70
- @links = {}
75
+ @authorization = JSON.parse(json)
76
+ end
71
77
 
72
- JSON.parse(catalog)['links'].each do |link|
73
- uri = URI.parse(link['uri'])
74
- uri.query = nil
78
+ def authorization_links
79
+ return @authorization_links if defined?(@authorization_links)
75
80
 
76
- @links[keyify(link['rel'])] = uri.to_s
77
- end
81
+ @authorization_links = {
82
+ authorization: authorization['authorization_endpoint'],
83
+ token: authorization['token_endpoint'],
84
+ organizations: "https://connections.deere.com/connections/#{api_key}/select-organizations",
85
+ }
86
+ end
78
87
 
79
- @links
88
+ def scopes
89
+ return @scopes if defined?(@scopes)
90
+ @scopes = authorization['scopes_supported']
80
91
  end
81
92
 
82
- def header
83
- @header ||= {accept: 'application/vnd.deere.axiom.v3+json'}
93
+ def headers
94
+ @headers ||= {accept: 'application/vnd.deere.axiom.v3+json'}
84
95
  end
85
96
 
86
97
  def keyify key_name
@@ -22,7 +22,8 @@ module MyJohnDeereApi::Helpers::CaseConversion
22
22
  def camelize(something)
23
23
  something = something.to_s if something.is_a?(Symbol)
24
24
 
25
- if something.is_a?(String)
25
+ case something
26
+ when String
26
27
  list = something.strip.split(/[_\s]+/)
27
28
 
28
29
  # preserve case of the first element
@@ -30,8 +31,10 @@ module MyJohnDeereApi::Helpers::CaseConversion
30
31
  new_list += list.map(&:capitalize)
31
32
 
32
33
  new_list.join('')
33
- elsif something.is_a?(Hash)
34
+ when Hash
34
35
  something.transform_keys{ |key| camelize(key) }
36
+ when Array
37
+ something.map{|element| camelize(element)}
35
38
  end
36
39
  end
37
40
  end
@@ -6,7 +6,7 @@ module MyJohnDeereApi::Helpers::UriHelpers
6
6
  ##
7
7
  # extract just the path from the uri, excluding the platform prefix
8
8
  def uri_path(uri)
9
- URI.parse(uri).path.gsub(/^\/platform/, '')
9
+ URI.parse(uri).path
10
10
  end
11
11
 
12
12
  ##
@@ -5,7 +5,7 @@ module MyJohnDeereApi
5
5
  private
6
6
 
7
7
  def map_attributes(record)
8
- @name = record['name']
8
+ @name = record['name']
9
9
  end
10
10
 
11
11
  def expected_record_type
@@ -7,41 +7,40 @@ module MyJohnDeereApi
7
7
  request_methods: [:get, :post, :put, :delete],
8
8
  retry_delay_exponent: 2,
9
9
  max_retries: 12,
10
- retry_codes: ['429', '503'],
11
- valid_codes: ['200', '201', '204'],
10
+ retry_codes: [429, 503],
11
+ valid_codes: [200, 201, 204],
12
12
  }
13
13
 
14
14
  def initialize(object, options={})
15
15
  @object = object
16
16
 
17
- # defaults that can be used as-is
18
- [:request_methods, :retry_delay_exponent, :max_retries].each do |option|
17
+ # options that can be used as-is
18
+ [:request_methods, :retry_delay_exponent, :max_retries, :retry_codes, :valid_codes].each do |option|
19
19
  instance_variable_set(:"@#{option}", options[option] || DEFAULTS[option])
20
20
  end
21
21
 
22
- # defaults that require casting as string arrays
22
+ # options that require casting as integer arrays
23
23
  [:retry_codes, :valid_codes].each do |option|
24
- instance_variable_set(:"@#{option}", (options[option] || DEFAULTS[option]).map(&:to_s))
24
+ instance_variable_set(:"@#{option}", (options[option] || DEFAULTS[option]).map(&:to_i))
25
25
  end
26
26
  end
27
27
 
28
28
  def request(method_name, *args)
29
29
  retries = 0
30
30
  result = object.send(method_name, *args)
31
-
32
- while retry_codes.include?(result.code)
31
+ while retry_codes.include?(result.status)
33
32
  if retries >= max_retries
34
- raise MaxRetriesExceededError.new(method_name, "#{result.code} #{result.message}")
33
+ raise MaxRetriesExceededError.new(method_name, "#{result.status} #{result.response.reason_phrase}")
35
34
  end
36
35
 
37
- delay = [result['retry-after'].to_i, retry_delay_exponent ** retries].max
36
+ delay = [result.headers['retry-after'].to_i, retry_delay_exponent ** retries].max
38
37
  sleep(delay)
39
38
 
40
39
  result = object.send(method_name, *args)
41
40
  retries += 1
42
41
  end
43
42
 
44
- unless valid_codes.include?(result.code)
43
+ unless valid_codes.include?(result.status)
45
44
  raise InvalidResponseError.new(result)
46
45
  end
47
46