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 +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/build.yml +8 -6
- data/CHANGELOG.md +20 -0
- data/Gemfile.lock +12 -12
- data/Gemfile_ar60 +5 -0
- data/{Gemfile_ar_master → Gemfile_ar_main} +0 -0
- data/README.md +15 -13
- data/dev.yml +11 -0
- data/lib/active_resource/detailed_log_subscriber.rb +4 -0
- data/lib/shopify_api/hmac_params.rb +33 -0
- data/lib/shopify_api/session.rb +11 -9
- data/lib/shopify_api/version.rb +1 -1
- data/lib/shopify_api.rb +5 -0
- data/service.yml +2 -5
- data/shopify_api.gemspec +2 -2
- data/test/detailed_log_subscriber_test.rb +1 -1
- data/test/fulfillment_order_test.rb +2 -2
- data/test/hmac_params_test.rb +25 -0
- data/test/meta_test.rb +2 -2
- data/test/session_test.rb +91 -8
- metadata +10 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 933903ae7a032f32236d6ab88916650e03e4b0288f65208babba3141816454ac
|
4
|
+
data.tar.gz: 7260bf183dfe4327d6d94880dd67745bcc0f413576c7c2e95aa143a0e7649721
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a54d8ee191a8fc3bfa8f11a0df12106ba3b1f46d78d17d4168988e6c56563532ebd1225e076d24e19d90925cc17bc59c1d8cf96be40fd009cacd0251965be9b
|
7
|
+
data.tar.gz: 6463e8c51171d93d226adab9441530397bc334aaabf36fa00ab8a9b0e6128e46fa768957faa132971bf402fc9e3618d8b87f7f45d3f538f545391fbc3fd1da23
|
data/.github/CODEOWNERS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
* @shopify/
|
1
|
+
* @shopify/core-build-learn
|
data/.github/workflows/build.yml
CHANGED
@@ -12,17 +12,19 @@ jobs:
|
|
12
12
|
strategy:
|
13
13
|
matrix:
|
14
14
|
version:
|
15
|
-
- 2.
|
16
|
-
- 2.
|
17
|
-
-
|
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
|
-
-
|
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.
|
5
|
-
activeresource (>= 4.1.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.
|
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.
|
45
|
-
graphql-client (0.
|
44
|
+
graphql (1.13.4)
|
45
|
+
graphql-client (0.17.0)
|
46
46
|
activesupport (>= 3.0)
|
47
|
-
graphql (~> 1.
|
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.
|
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.
|
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.
|
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.
|
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 (>=
|
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.
|
151
|
+
2.2.22
|
data/Gemfile_ar60
ADDED
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
|
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
@@ -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
|
data/lib/shopify_api/session.rb
CHANGED
@@ -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,
|
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.
|
186
|
+
URI.encode_www_form(params)
|
185
187
|
end
|
186
188
|
|
187
189
|
def access_token_request(code)
|
data/lib/shopify_api/version.rb
CHANGED
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
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"
|
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", ">=
|
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\"=>\"
|
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
|
-
].
|
43
|
+
].as_json
|
44
44
|
|
45
|
-
assert_equal versions, ShopifyAPI::Meta.admin_versions.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
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:
|
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: '
|
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: '
|
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
|
-
-
|
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.
|
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
|