shopify_api 9.3.0 → 9.5.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 73854704f72bca959d915cec44e6c54f794d3d5d60dc97f343f4993a88e08a55
4
- data.tar.gz: a85ee03d769fddbbb6f5df673cffedbd5b6f2706c3f3a27ba2096fbde622585e
3
+ metadata.gz: 933903ae7a032f32236d6ab88916650e03e4b0288f65208babba3141816454ac
4
+ data.tar.gz: 7260bf183dfe4327d6d94880dd67745bcc0f413576c7c2e95aa143a0e7649721
5
5
  SHA512:
6
- metadata.gz: d98a3b0c0c57787436e557aa66f3f5d7de4f2373ae7e97eb0dd3283da7935f0c30d561ed690b132c54a7336ad040d25e2be66888892b05e1ca8844ad0eb6266f
7
- data.tar.gz: 6d11786b5b42c116370c5696ae8cf43b3ee111a6ab344b3d0d0e7c738510323b74f7d7df6441c9c83dba6286d53218185c41ef211eb5f61b64a95c807ea1c9c9
6
+ metadata.gz: 5a54d8ee191a8fc3bfa8f11a0df12106ba3b1f46d78d17d4168988e6c56563532ebd1225e076d24e19d90925cc17bc59c1d8cf96be40fd009cacd0251965be9b
7
+ data.tar.gz: 6463e8c51171d93d226adab9441530397bc334aaabf36fa00ab8a9b0e6128e46fa768957faa132971bf402fc9e3618d8b87f7f45d3f538f545391fbc3fd1da23
data/.github/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- * @shopify/platform-dev-tools-education
1
+ * @shopify/core-build-learn
@@ -12,17 +12,19 @@ jobs:
12
12
  strategy:
13
13
  matrix:
14
14
  version:
15
- - 2.4
16
- - 2.5
17
- - 2.6
18
- - 2.7
15
+ - "2.6"
16
+ - "2.7"
17
+ - "3.0"
19
18
  gemfile:
20
19
  - Gemfile_ar41
21
20
  - Gemfile_ar50
22
21
  - Gemfile_ar51
23
- - Gemfile_ar_master
22
+ - Gemfile_ar60
23
+ - Gemfile_ar_main
24
24
  exclude:
25
- - version: 2.7
25
+ - version: "2.7"
26
+ gemfile: Gemfile_ar41
27
+ - version: "3.0"
26
28
  gemfile: Gemfile_ar41
27
29
  steps:
28
30
  - uses: actions/checkout@v2
data/CHANGELOG.md CHANGED
@@ -1,3 +1,23 @@
1
+ ## Unreleased
2
+
3
+ ## Version 9.5.1
4
+
5
+ - [#891](https://github.com/Shopify/shopify_api/pull/891) Removed the upper bound on the `activeresource` dependency to allow apps to use the latest version
6
+
7
+ ## Version 9.5
8
+
9
+ * [#883](https://github.com/Shopify/shopify_api/pull/883) Add support for Ruby 3.0
10
+
11
+ ## Version 9.4.1
12
+
13
+ * [#847](https://github.com/Shopify/shopify_api/pull/847) Update `create_permission_url` method to use grant_options
14
+ * [#852](https://github.com/Shopify/shopify_api/pull/852) Bumping kramdown to fix a security vulnerability
15
+
16
+ ## Version 9.4.0
17
+
18
+ * [#843](https://github.com/Shopify/shopify_api/pull/843) Introduce a new `access_scopes` attribute on the Session class.
19
+ * Specifying this in the Session constructor is optional. By default, this attribute returns `nil`.
20
+
1
21
  ## Version 9.3.0
2
22
 
3
23
  * [#797](https://github.com/Shopify/shopify_api/pull/797) Release new Endpoint `fulfillment_order.open` and `fulfillment_order.reschedule`.
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify_api (9.3.0)
5
- activeresource (>= 4.1.0, < 6.0.0)
4
+ shopify_api (9.5.1)
5
+ activeresource (>= 4.1.0)
6
6
  graphql-client
7
7
  rack
8
8
 
@@ -25,7 +25,7 @@ GEM
25
25
  minitest (~> 5.1)
26
26
  tzinfo (~> 1.1)
27
27
  zeitwerk (~> 2.2, >= 2.2.2)
28
- addressable (2.7.0)
28
+ addressable (2.8.0)
29
29
  public_suffix (>= 2.0.2, < 5.0)
30
30
  ast (2.4.1)
31
31
  builder (3.2.4)
@@ -41,10 +41,10 @@ GEM
41
41
  eventmachine (1.2.7)
42
42
  ffi (1.12.2)
43
43
  forwardable-extended (2.6.0)
44
- graphql (1.12.3)
45
- graphql-client (0.16.0)
44
+ graphql (1.13.4)
45
+ graphql-client (0.17.0)
46
46
  activesupport (>= 3.0)
47
- graphql (~> 1.8)
47
+ graphql (~> 1.10)
48
48
  hashdiff (1.0.1)
49
49
  http_parser.rb (0.6.0)
50
50
  i18n (1.8.2)
@@ -68,7 +68,7 @@ GEM
68
68
  sassc (> 2.0.1, < 3.0)
69
69
  jekyll-watch (2.2.1)
70
70
  listen (~> 3.0)
71
- kramdown (2.3.0)
71
+ kramdown (2.3.1)
72
72
  rexml
73
73
  kramdown-parser-gfm (1.1.0)
74
74
  kramdown (~> 2.0)
@@ -78,7 +78,7 @@ GEM
78
78
  rb-inotify (~> 0.9, >= 0.9.10)
79
79
  mercenary (0.4.0)
80
80
  method_source (1.0.0)
81
- minitest (5.14.1)
81
+ minitest (5.14.4)
82
82
  mocha (1.11.2)
83
83
  parallel (1.19.2)
84
84
  parser (2.7.2.0)
@@ -91,7 +91,7 @@ GEM
91
91
  pry-byebug (3.9.0)
92
92
  byebug (~> 11.0)
93
93
  pry (~> 0.13.0)
94
- public_suffix (4.0.5)
94
+ public_suffix (4.0.6)
95
95
  rack (2.2.3)
96
96
  rainbow (3.0.0)
97
97
  rake (13.0.1)
@@ -99,7 +99,7 @@ GEM
99
99
  rb-inotify (0.10.1)
100
100
  ffi (~> 1.0)
101
101
  regexp_parser (1.8.2)
102
- rexml (3.2.4)
102
+ rexml (3.2.5)
103
103
  rouge (3.19.0)
104
104
  rubocop (0.93.1)
105
105
  parallel (~> 1.10)
@@ -137,7 +137,7 @@ PLATFORMS
137
137
  DEPENDENCIES
138
138
  activeresource (~> 5.1)
139
139
  jekyll
140
- minitest (>= 4.0)
140
+ minitest (>= 5.14)
141
141
  mocha (>= 1.4.0)
142
142
  pry
143
143
  pry-byebug
@@ -148,4 +148,4 @@ DEPENDENCIES
148
148
  webmock
149
149
 
150
150
  BUNDLED WITH
151
- 2.1.4
151
+ 2.2.22
data/Gemfile_ar60 ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "activeresource", "~> 6.0"
File without changes
data/README.md CHANGED
@@ -61,6 +61,8 @@ For more information and detailed documentation about the API visit https://deve
61
61
 
62
62
  This gem requires Ruby 2.4 as of version 7.0.
63
63
 
64
+ **Note**: when we release the next major version, we will be dropping support for Ruby 2.4 and 2.5, which have now reached EOL, as per [the releases page](https://www.ruby-lang.org/en/downloads/branches/).
65
+
64
66
  ## Installation
65
67
 
66
68
  Add `shopify_api` to your `Gemfile`:
@@ -87,13 +89,13 @@ ShopifyAPI sessions need to be configured with a fully authorized URL of a parti
87
89
 
88
90
  ### 1) Create an app
89
91
 
90
- First, create a new application in either the partners admin or your store admin.
92
+ First, create a new application in either the partners admin or your store admin.
91
93
 
92
94
  **Private apps** are used for merchant-owned scripts and apps that run silently in the background on a single shop. Private apps aren't able to render any content in the admin. Private apps are created through the store admin.
93
95
 
94
96
  **Custom apps** are also used for a single shop, but they have access to [app extensions](https://shopify.dev/docs/app-extensions) that allow the app to render content in the admin and are managed and created through the partners dashboard.
95
97
 
96
- **Public apps** can be installed on many stores, and can be added to the Shopify App Store to generate revenue for the developer.
98
+ **Public apps** can be installed on many stores, and can be added to the Shopify App Store to generate revenue for the developer.
97
99
 
98
100
  For a private app, you'll need the API_KEY and the PASSWORD; otherwise, you'll need the API_KEY and SHARED_SECRET.
99
101
 
@@ -112,7 +114,7 @@ For a private App you just need to set the base site url as follows:
112
114
  That's it; you're done! Next, skip to step 6 and start using the API!
113
115
 
114
116
  ### 2B) Public and Custom Apps
115
-
117
+
116
118
  For public and custom apps, you will need to supply two parameters to the Session class before you instantiate it:
117
119
 
118
120
  ```ruby
@@ -130,11 +132,11 @@ Public and Custom apps need an access token from each shop to access that shop's
130
132
  ```ruby
131
133
  # We need to instantiate the session object before using it
132
134
  shopify_session = ShopifyAPI::Session.new(domain: "#{SHOP_NAME}.myshopify.com", api_version: api_version, token: nil)
133
-
135
+
134
136
  # Then, create a permission URL with the session
135
137
  permission_url = shopify_session.create_permission_url(scope, "https://my_redirect_uri.com", { state: "My Nonce" })
136
138
  ```
137
-
139
+
138
140
  After creating the permission URL, the user should be directed to this URL to approve the app.
139
141
 
140
142
  Under the hood, the `create_permission_url` method is preparing the app to make the following request :
@@ -149,7 +151,7 @@ Under the hood, the `create_permission_url` method is preparing the app to make
149
151
  * ``scope`` – Required – The list of required scopes (explained here: https://shopify.dev/tutorials/authenticate-with-oauth#scopes)
150
152
  * ``redirect_uri`` – Required – The URL where you want to redirect the users after they authorize the client. The complete URL specified here must be identical to one of the Application Redirect URLs set in the app's section of the Partners dashboard.
151
153
  * ``state`` – Optional – A randomly selected value provided by your application, which is unique for each authorization request. During the OAuth callback phase, your application must check that this value matches the one you provided during authorization. [This mechanism is essential for the security of your application](https://tools.ietf.org/html/rfc6819#section-3.6).
152
- * ``grant_options[]`` - Optional - Set this parameter to `per-user` to receive an access token that respects the user's permission level when making API requests (called online access). We strongly recommend using this parameter for embedded apps.
154
+ * ``grant_options`` - Optional - Set this parameter to `per-user` to receive an access token that respects the user's permission level when making API requests (called online access). We strongly recommend using this parameter for embedded apps.
153
155
 
154
156
  ### 4) Trading your `code` for an access token.
155
157
 
@@ -167,8 +169,8 @@ Once authorized, the shop redirects the owner to the return URL of your applicat
167
169
  token = shopify_session.request_token(params)
168
170
  ```
169
171
 
170
- This method will save the token to the session object and return it. All fields returned by Shopify, other than the access token itself, are stored in the session's `extra` attribute. For a list of all fields returned by Shopify, read [our OAuth documentation](https://shopify.dev/tutorials/authenticate-with-oauth#confirming-installation).
171
-
172
+ This method will save the token to the session object and return it. All fields returned by Shopify, other than the access token itself, are stored in the session's `extra` attribute. For a list of all fields returned by Shopify, read [our OAuth documentation](https://shopify.dev/tutorials/authenticate-with-oauth#confirming-installation).
173
+
172
174
  If you prefer to exchange the token manually, you can make a POST request to the shop with the following parameters :
173
175
 
174
176
  ```
@@ -198,7 +200,7 @@ Once authorized, the shop redirects the owner to the return URL of your applicat
198
200
  1) The list of scopes in `shopify_session.extra['scope']` is the same as you requested.
199
201
  2) If you requested an online-mode access token, `shopify_session.extra['associated_user']` must be present.
200
202
  Failing either of these tests means the end-user may have tampered with the URL parameters during the OAuth authentication phase. You should avoid using this access token and revoke it immediately. If you use the [`omniauth-shopify-oauth2`](https://github.com/Shopify/omniauth-shopify-oauth2) gem, these checks are done automatically for you.
201
-
203
+
202
204
  ### 5) Activating the session
203
205
 
204
206
  Once you have a token, simply pass in the `token` and `extra` hash (optional) when creating the session object:
@@ -333,7 +335,7 @@ If you were previously using Shopify's [activeresource fork](https://github.com/
333
335
 
334
336
  ## Bulk Operations
335
337
 
336
- With the GraphQL Admin API, you can use bulk operations to asynchronously fetch data in bulk. The API is designed to reduce complexity and improve performance when dealing with large volumes of data.
338
+ With the GraphQL Admin API, you can use bulk operations to asynchronously fetch data in bulk. The API is designed to reduce complexity and improve performance when dealing with large volumes of data.
337
339
 
338
340
  Instead of manually paginating results and managing a client-side throttle, you can instead run a bulk query operation. Shopify’s infrastructure does the hard work of executing your query, and then provides you with a URL where you can download all of the data.
339
341
 
@@ -465,7 +467,7 @@ end
465
467
 
466
468
  ## Pagination
467
469
 
468
- Shopify uses [Relative cursor-based pagination](https://shopify.dev/tutorials/make-paginated-requests-to-rest-admin-api) to provide more than a single page of results.
470
+ Shopify uses [Relative cursor-based pagination](https://shopify.dev/tutorials/make-paginated-requests-to-rest-admin-api) to provide more than a single page of results.
469
471
 
470
472
  ```ruby
471
473
  products = ShopifyAPI::Product.find(:all, params: { limit: 50 })
@@ -628,9 +630,9 @@ or you can even use our automated rake task for docker:
628
630
  bundle exec rake docker
629
631
  ```
630
632
 
631
- # Logging
633
+ # Logging
632
634
 
633
- Enable ActiveResource's logger with
635
+ Enable ActiveResource's logger with
634
636
 
635
637
  `export SHOPIFY_LOG_PATH={your_log_path}`
636
638
 
data/dev.yml ADDED
@@ -0,0 +1,11 @@
1
+ name: shopify-api
2
+
3
+ type: ruby
4
+
5
+ up:
6
+ - ruby: "3.0"
7
+ - bundler
8
+
9
+ commands:
10
+ test:
11
+ run: bundle exec rake test
@@ -3,6 +3,9 @@ module ActiveResource
3
3
  class DetailedLogSubscriber < ActiveSupport::LogSubscriber
4
4
  VERSION_EOL_WARNING_HEADER = 'x-shopify-api-version-warning'
5
5
  VERSION_DEPRECATION_HEADER = 'x-shopify-api-deprecated-reason'
6
+ SHOPIFY_ACCESS_TOKEN = 'X-Shopify-Access-Token'
7
+ FILTERED = '[FILTERED]'
8
+
6
9
  def request(event)
7
10
  log_request_response_details(event)
8
11
  warn_on_deprecated_header_or_version_eol_header(event)
@@ -17,6 +20,7 @@ module ActiveResource
17
20
  def log_request_response_details(event)
18
21
  data = event.payload[:data]
19
22
  headers = data.extract_options!
23
+ headers[SHOPIFY_ACCESS_TOKEN] = FILTERED
20
24
  request_body = data.first
21
25
 
22
26
  info("Request:\n#{request_body}") if request_body
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyAPI
4
+ module HmacParams
5
+ class << self
6
+ def encode(params)
7
+ params
8
+ .except(:signature, :hmac, :action, :controller)
9
+ .map { |k,v| sprintf("%s=%s", encode_key(k), encode_value(v)) }
10
+ .sort.join("&")
11
+ end
12
+
13
+ KEY_REGEXP = /([#{Regexp.escape("&=%")}])/n
14
+ def encode_key(key)
15
+ _escape(key.to_s, KEY_REGEXP)
16
+ end
17
+
18
+ VALUE_REGEXP = /([#{Regexp.escape("&%")}])/n
19
+ def encode_value(value)
20
+ _escape(value.to_s, VALUE_REGEXP)
21
+ end
22
+
23
+ private
24
+
25
+ def _escape(str, regex)
26
+ str = str.b
27
+ str.gsub!(regex) {"%%%02X" % $1.ord}
28
+ # %-escaped string should contain US-ASCII only
29
+ str.force_encoding(Encoding::US_ASCII)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -13,7 +13,7 @@ module ShopifyAPI
13
13
  self.myshopify_domain = 'myshopify.com'
14
14
 
15
15
  attr_accessor :domain, :token, :name, :extra
16
- attr_reader :api_version
16
+ attr_reader :api_version, :access_scopes
17
17
  alias_method :url, :domain
18
18
 
19
19
  class << self
@@ -71,7 +71,7 @@ module ShopifyAPI
71
71
  return false unless (signature = params[:hmac])
72
72
 
73
73
  calculated_signature = OpenSSL::HMAC.hexdigest(
74
- OpenSSL::Digest.new('SHA256'), secret, encoded_params_for_signature(params)
74
+ OpenSSL::Digest.new('SHA256'), secret, ShopifyAPI::HmacParams.encode(params)
75
75
  )
76
76
 
77
77
  Rack::Utils.secure_compare(calculated_signature, signature)
@@ -79,11 +79,6 @@ module ShopifyAPI
79
79
 
80
80
  private
81
81
 
82
- def encoded_params_for_signature(params)
83
- params = params.except(:signature, :hmac, :action, :controller)
84
- params.map { |k, v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}" }.sort.join('&')
85
- end
86
-
87
82
  def extract_current_session
88
83
  site = ShopifyAPI::Base.site.to_s
89
84
  token = ShopifyAPI::Base.headers['X-Shopify-Access-Token']
@@ -92,16 +87,18 @@ module ShopifyAPI
92
87
  end
93
88
  end
94
89
 
95
- def initialize(domain:, token:, api_version: ShopifyAPI::Base.api_version, extra: {})
90
+ def initialize(domain:, token:, access_scopes: nil, api_version: ShopifyAPI::Base.api_version, extra: {})
96
91
  self.domain = self.class.prepare_domain(domain)
97
92
  self.api_version = api_version
98
93
  self.token = token
94
+ self.access_scopes = access_scopes
99
95
  self.extra = extra
100
96
  end
101
97
 
102
98
  def create_permission_url(scope, redirect_uri, options = {})
103
99
  params = { client_id: api_key, scope: ShopifyAPI::ApiAccess.new(scope).to_s, redirect_uri: redirect_uri }
104
100
  params[:state] = options[:state] if options[:state]
101
+ params["grant_options[]".to_sym] = options[:grant_options] if options[:grant_options]
105
102
  construct_oauth_url("authorize", params)
106
103
  end
107
104
 
@@ -180,8 +177,13 @@ module ShopifyAPI
180
177
 
181
178
  private
182
179
 
180
+ def access_scopes=(access_scopes)
181
+ return unless access_scopes
182
+ @access_scopes = ShopifyAPI::ApiAccess.new(access_scopes)
183
+ end
184
+
183
185
  def parameterize(params)
184
- URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))
186
+ URI.encode_www_form(params)
185
187
  end
186
188
 
187
189
  def access_token_request(code)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ShopifyAPI
3
- VERSION = "9.3.0"
3
+ VERSION = "9.5.1"
4
4
  end
data/lib/shopify_api.rb CHANGED
@@ -12,6 +12,10 @@ require 'active_resource/json_errors'
12
12
  require 'shopify_api/paginated_collection'
13
13
  require 'shopify_api/disable_prefix_check'
14
14
 
15
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.6")
16
+ puts("\nshopify_api: NOTE: Support for Ruby #{RUBY_VERSION} will be dropped in the next major release. Please update to Ruby 2.6 or newer before updating this gem.\n\n")
17
+ end
18
+
15
19
  module ShopifyAPI
16
20
  include Limits
17
21
  end
@@ -21,6 +25,7 @@ require 'shopify_api/metafields'
21
25
  require 'shopify_api/countable'
22
26
  require 'shopify_api/resources'
23
27
  require 'shopify_api/session'
28
+ require 'shopify_api/hmac_params'
24
29
  require 'shopify_api/api_access'
25
30
  require 'shopify_api/message_enricher'
26
31
  require 'shopify_api/connection'
data/service.yml CHANGED
@@ -1,8 +1,5 @@
1
1
  audience: partner
2
2
  classification: library
3
- org_line: App & Partner Platform
4
- owners:
5
- - Shopify/app-partner-dev-tools-education
6
3
  slack_channels:
7
- - dev-tools-education
8
- - help-api-patterns
4
+ - core-build-learn
5
+ - help-api-patterns
data/shopify_api.gemspec CHANGED
@@ -32,13 +32,13 @@ Gem::Specification.new do |s|
32
32
 
33
33
  s.required_ruby_version = ">= 2.4"
34
34
 
35
- s.add_runtime_dependency("activeresource", ">= 4.1.0", "< 6.0.0")
35
+ s.add_runtime_dependency("activeresource", ">= 4.1.0")
36
36
  s.add_runtime_dependency("rack")
37
37
  s.add_runtime_dependency("graphql-client")
38
38
 
39
39
  s.add_development_dependency("mocha", ">= 1.4.0")
40
40
  s.add_development_dependency("webmock")
41
- s.add_development_dependency("minitest", ">= 4.0")
41
+ s.add_development_dependency("minitest", ">= 5.14")
42
42
  s.add_development_dependency("rake")
43
43
  s.add_development_dependency("timecop")
44
44
  s.add_development_dependency("rubocop-shopify")
@@ -14,7 +14,7 @@ class LogSubscriberTest < Test::Unit::TestCase
14
14
  @ua_header = "\"User-Agent\"=>\"ShopifyAPI/#{ShopifyAPI::VERSION} " \
15
15
  "ActiveResource/#{ActiveResource::VERSION::STRING} Ruby/#{RUBY_VERSION}\""
16
16
  @request_headers = "Headers: {\"Accept\"=>\"application/json\", " \
17
- "#{@ua_header}, \"X-Shopify-Access-Token\"=>\"access_token\"}"
17
+ "#{@ua_header}, \"X-Shopify-Access-Token\"=>\"[FILTERED]\"}"
18
18
 
19
19
  ShopifyAPI::Base.clear_session
20
20
  fake(
@@ -315,7 +315,7 @@ class FulFillmentOrderTest < Test::Unit::TestCase
315
315
  fulfillment_order_line_items: [{ id: 1, quantity: 1 }],
316
316
  message: "Fulfill this FO, please.",
317
317
  }
318
- response_fulfillment_orders = fulfillment_order.request_fulfillment(params)
318
+ response_fulfillment_orders = fulfillment_order.request_fulfillment(**params)
319
319
 
320
320
  assert_equal('closed', fulfillment_order.status)
321
321
  assert_equal(3, response_fulfillment_orders.size)
@@ -367,7 +367,7 @@ class FulFillmentOrderTest < Test::Unit::TestCase
367
367
  fulfillment_order_line_items: [{ id: 1, quantity: 1 }],
368
368
  message: "Fulfill this FO, please.",
369
369
  }
370
- response_fulfillment_orders = fulfillment_order.request_fulfillment(params)
370
+ response_fulfillment_orders = fulfillment_order.request_fulfillment(**params)
371
371
 
372
372
  assert_equal('closed', fulfillment_order.status)
373
373
  assert_equal(3, response_fulfillment_orders.size)
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ require 'test_helper'
3
+
4
+ class HmacParamsTest < Test::Unit::TestCase
5
+ test "cgi param keys are prepared for hmac validation by encoding equals, ampersand, and percent characters" do
6
+ assert_equal(
7
+ "abcd%26%3D%251234",
8
+ ShopifyAPI::HmacParams.encode_key("abcd&=%1234")
9
+ )
10
+ end
11
+
12
+ test "cgi param values are prepared for hmac validation by encoding ampersand and percent characters" do
13
+ assert_equal(
14
+ "abcd%26=%251234",
15
+ ShopifyAPI::HmacParams.encode_value("abcd&=%1234")
16
+ )
17
+ end
18
+
19
+ test "cgi params are encoded properly for hmac validation" do
20
+ assert_equal(
21
+ "abcd%26%3D%251234=abcd%26=%251234",
22
+ ShopifyAPI::HmacParams.encode({"abcd&=%1234" => "abcd&=%1234"})
23
+ )
24
+ end
25
+ end
data/test/meta_test.rb CHANGED
@@ -40,8 +40,8 @@ class ApiVersionTest < Test::Unit::TestCase
40
40
  "display_name": "unstable",
41
41
  "supported": false,
42
42
  },
43
- ].to_json
43
+ ].as_json
44
44
 
45
- assert_equal versions, ShopifyAPI::Meta.admin_versions.to_json
45
+ assert_equal versions, ShopifyAPI::Meta.admin_versions.as_json
46
46
  end
47
47
  end
data/test/session_test.rb CHANGED
@@ -54,6 +54,39 @@ class SessionTest < Test::Unit::TestCase
54
54
  assert(session.valid?)
55
55
  end
56
56
 
57
+ test "be valid with nil access_scopes" do
58
+ session = ShopifyAPI::Session.new(
59
+ domain: "testshop.myshopify.com",
60
+ token: "any-token",
61
+ api_version: any_api_version,
62
+ access_scopes: nil
63
+ )
64
+
65
+ assert(session.valid?)
66
+ end
67
+
68
+ test "be valid with string of access_scopes" do
69
+ session = ShopifyAPI::Session.new(
70
+ domain: "testshop.myshopify.com",
71
+ token: "any-token",
72
+ api_version: any_api_version,
73
+ access_scopes: "read_products, write_orders"
74
+ )
75
+
76
+ assert(session.valid?)
77
+ end
78
+
79
+ test "be valid with a collection of access_scopes" do
80
+ session = ShopifyAPI::Session.new(
81
+ domain: "testshop.myshopify.com",
82
+ token: "any-token",
83
+ api_version: any_api_version,
84
+ access_scopes: %w(read_products write_orders)
85
+ )
86
+
87
+ assert(session.valid?)
88
+ end
89
+
57
90
  test "not raise error without params" do
58
91
  assert_nothing_raised do
59
92
  ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: "any-token", api_version: any_api_version)
@@ -61,7 +94,7 @@ class SessionTest < Test::Unit::TestCase
61
94
  end
62
95
 
63
96
  test "ignore everything but the subdomain in the shop" do
64
- assert_equal(
97
+ assert_equal_uri(
65
98
  "https://testshop.myshopify.com",
66
99
  ShopifyAPI::Session.new(
67
100
  domain: "http://user:pass@testshop.notshopify.net/path",
@@ -72,7 +105,7 @@ class SessionTest < Test::Unit::TestCase
72
105
  end
73
106
 
74
107
  test "append the myshopify domain if not given" do
75
- assert_equal(
108
+ assert_equal_uri(
76
109
  "https://testshop.myshopify.com",
77
110
  ShopifyAPI::Session.new(domain: "testshop", token: "any-token", api_version: any_api_version).site
78
111
  )
@@ -84,6 +117,36 @@ class SessionTest < Test::Unit::TestCase
84
117
  end
85
118
  end
86
119
 
120
+ test "provides default nil access_scopes attribute" do
121
+ session = ShopifyAPI::Session.new(
122
+ domain: "testshop.myshopify.com",
123
+ token: "any-token",
124
+ api_version: any_api_version
125
+ )
126
+ assert_nil session.access_scopes
127
+ end
128
+
129
+ test "provides specified nil access_scopes attribute" do
130
+ session = ShopifyAPI::Session.new(
131
+ domain: "testshop.myshopify.com",
132
+ token: "any-token",
133
+ access_scopes: "read_products",
134
+ api_version: any_api_version
135
+ )
136
+ assert_equal "read_products", session.access_scopes.to_s
137
+ end
138
+
139
+ test "session instantiation raises error if bad access scopes are provided" do
140
+ assert_raises NoMethodError do
141
+ ShopifyAPI::Session.new(
142
+ domain: "testshop.myshopify.com",
143
+ token: "any-token",
144
+ access_scopes: { bad_input: "bad_input" },
145
+ api_version: any_api_version
146
+ )
147
+ end
148
+ end
149
+
87
150
  test "raise error if params passed but signature omitted" do
88
151
  assert_raises(ShopifyAPI::ValidationException) do
89
152
  session = ShopifyAPI::Session.new(domain: "testshop.myshopify.com", token: nil, api_version: any_api_version)
@@ -220,7 +283,7 @@ class SessionTest < Test::Unit::TestCase
220
283
  )
221
284
  scope = ["write_products"]
222
285
  permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
223
- assert_equal(
286
+ assert_equal_uri(
224
287
  "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&" \
225
288
  "scope=write_products&redirect_uri=http://my_redirect_uri.com",
226
289
  permission_url
@@ -236,7 +299,7 @@ class SessionTest < Test::Unit::TestCase
236
299
  )
237
300
  scope = ["write_products", "write_customers"]
238
301
  permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
239
- assert_equal(
302
+ assert_equal_uri(
240
303
  "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&" \
241
304
  "scope=write_products,write_customers&redirect_uri=http://my_redirect_uri.com",
242
305
  permission_url
@@ -252,7 +315,7 @@ class SessionTest < Test::Unit::TestCase
252
315
  )
253
316
  scope = []
254
317
  permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com")
255
- assert_equal(
318
+ assert_equal_uri(
256
319
  "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&" \
257
320
  "scope=&redirect_uri=http://my_redirect_uri.com",
258
321
  permission_url
@@ -268,9 +331,25 @@ class SessionTest < Test::Unit::TestCase
268
331
  )
269
332
  scope = []
270
333
  permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com", state: "My nonce")
271
- assert_equal(
334
+ assert_equal_uri(
272
335
  "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&" \
273
- "scope=&redirect_uri=http://my_redirect_uri.com&state=My%20nonce",
336
+ "scope=&redirect_uri=http://my_redirect_uri.com&state=My+nonce",
337
+ permission_url
338
+ )
339
+ end
340
+
341
+ test "create_permission_url returns correct url with grant_options[]" do
342
+ ShopifyAPI::Session.setup(api_key: "My_test_key", secret: "My test secret")
343
+ session = ShopifyAPI::Session.new(
344
+ domain: 'http://localhost.myshopify.com',
345
+ token: 'any-token',
346
+ api_version: any_api_version
347
+ )
348
+ scope = []
349
+ permission_url = session.create_permission_url(scope, "http://my_redirect_uri.com", grant_options: "per-user")
350
+ assert_equal_uri(
351
+ "https://localhost.myshopify.com/admin/oauth/authorize?client_id=My_test_key&" \
352
+ "scope=&redirect_uri=http://my_redirect_uri.com&grant_options[]=per-user",
274
353
  permission_url
275
354
  )
276
355
  end
@@ -301,7 +380,7 @@ class SessionTest < Test::Unit::TestCase
301
380
  token: "any-token",
302
381
  api_version: any_api_version
303
382
  )
304
- assert_equal("https://testshop.myshopify.com", session.site)
383
+ assert_equal_uri("https://testshop.myshopify.com", session.site)
305
384
  end
306
385
 
307
386
  test "return_token_if_signature_is_valid" do
@@ -539,6 +618,10 @@ class SessionTest < Test::Unit::TestCase
539
618
 
540
619
  private
541
620
 
621
+ def assert_equal_uri(expected, actual)
622
+ assert_equal(Addressable::URI.parse(expected), Addressable::URI.parse(actual))
623
+ end
624
+
542
625
  def make_sorted_params(params)
543
626
  params.with_indifferent_access.except(
544
627
  :signature, :hmac, :action, :controller
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.3.0
4
+ version: 9.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2022-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeresource
@@ -17,9 +17,6 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: 4.1.0
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: 6.0.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +24,6 @@ dependencies:
27
24
  - - ">="
28
25
  - !ruby/object:Gem::Version
29
26
  version: 4.1.0
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: 6.0.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: rack
35
29
  requirement: !ruby/object:Gem::Requirement
@@ -92,14 +86,14 @@ dependencies:
92
86
  requirements:
93
87
  - - ">="
94
88
  - !ruby/object:Gem::Version
95
- version: '4.0'
89
+ version: '5.14'
96
90
  type: :development
97
91
  prerelease: false
98
92
  version_requirements: !ruby/object:Gem::Requirement
99
93
  requirements:
100
94
  - - ">="
101
95
  - !ruby/object:Gem::Version
102
- version: '4.0'
96
+ version: '5.14'
103
97
  - !ruby/object:Gem::Dependency
104
98
  name: rake
105
99
  requirement: !ruby/object:Gem::Requirement
@@ -198,12 +192,14 @@ files:
198
192
  - Gemfile_ar41
199
193
  - Gemfile_ar50
200
194
  - Gemfile_ar51
201
- - Gemfile_ar_master
195
+ - Gemfile_ar60
196
+ - Gemfile_ar_main
202
197
  - LICENSE
203
198
  - README.md
204
199
  - RELEASING
205
200
  - Rakefile
206
201
  - SECURITY.md
202
+ - dev.yml
207
203
  - docker-compose.yml
208
204
  - docs/_config.yml
209
205
  - docs/_includes/footer.html
@@ -225,6 +221,7 @@ files:
225
221
  - lib/shopify_api/graphql/http_client.rb
226
222
  - lib/shopify_api/graphql/railtie.rb
227
223
  - lib/shopify_api/graphql/task.rake
224
+ - lib/shopify_api/hmac_params.rb
228
225
  - lib/shopify_api/limits.rb
229
226
  - lib/shopify_api/message_enricher.rb
230
227
  - lib/shopify_api/meta.rb
@@ -487,6 +484,7 @@ files:
487
484
  - test/gift_card_test.rb
488
485
  - test/graphql/http_client_test.rb
489
486
  - test/graphql_test.rb
487
+ - test/hmac_params_test.rb
490
488
  - test/image_test.rb
491
489
  - test/inventory_level_test.rb
492
490
  - test/lib/webmock_extensions/last_request.rb
@@ -547,7 +545,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
547
545
  - !ruby/object:Gem::Version
548
546
  version: '0'
549
547
  requirements: []
550
- rubygems_version: 3.0.3
548
+ rubygems_version: 3.2.20
551
549
  signing_key:
552
550
  specification_version: 4
553
551
  summary: ShopifyAPI is a lightweight gem for accessing the Shopify admin REST web