omniauth-shopify-oauth2 1.1.14 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/build.yml +25 -0
- data/Gemfile +8 -0
- data/README.md +55 -1
- data/SECURITY.md +59 -0
- data/example/config.ru +1 -0
- data/lib/omniauth/shopify/version.rb +1 -1
- data/lib/omniauth/strategies/shopify.rb +59 -10
- data/omniauth-shopify-oauth2.gemspec +7 -2
- data/spec/omniauth/strategies/shopify_spec.rb +82 -1
- data/test/integration_test.rb +206 -34
- data/test/test_helper.rb +1 -0
- metadata +40 -10
- data/.travis.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0031b4b2dbba2b3b01f7805026f5a1a1043dd154192102244563ad248090f0ce
|
4
|
+
data.tar.gz: 3fd9935fe819c0bc35813d08c0c8e41baa1de43e8333fa3c1f7c494e068336c1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 819569da31d85ea93405929aab51022a4fdfdb998524de8f39d616bfaace0bac71d193b82414c4b04dcad06f0fcc81081079a6d1e10b82a4509eeed81c99888e
|
7
|
+
data.tar.gz: 86a9888309a4d6e46f80d56455469160a056aeb961beefc5aefc9f2ff0df42689cb4d349bac3212219093554aa534a841fd50127ab4af6faa7952dc4413194b8
|
data/.github/probots.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
name: Ruby ${{ matrix.version }}
|
10
|
+
strategy:
|
11
|
+
matrix:
|
12
|
+
version: [2.5.0, 2.7.1]
|
13
|
+
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v2
|
16
|
+
- name: Set up Ruby ${{ matrix.version }}
|
17
|
+
uses: ruby/setup-ruby@v1
|
18
|
+
with:
|
19
|
+
ruby-version: ${{ matrix.version }}
|
20
|
+
bundler-cache: true
|
21
|
+
- name: Install dependencies
|
22
|
+
run: bundle
|
23
|
+
- name: Run Tests
|
24
|
+
run: bundle exec rake
|
25
|
+
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
[![Build Status](https://
|
1
|
+
[![Build Status](https://github.com/Shopify/omniauth-shopify-oauth2/workflows/CI/badge.svg?branch=master)](https://github.com/Shopify/omniauth-shopify-oauth2/actions)
|
2
2
|
|
3
3
|
# OmniAuth Shopify
|
4
4
|
|
@@ -26,8 +26,49 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
26
26
|
end
|
27
27
|
```
|
28
28
|
|
29
|
+
Authenticate the user by having them visit /auth/shopify with a `shop` query parameter of their shop's myshopify.com domain. For example, the following form could be used
|
30
|
+
|
31
|
+
```html
|
32
|
+
<form action="/auth/shopify" method="get">
|
33
|
+
<label for="shop">Enter your store's URL:</label>
|
34
|
+
<input type="text" name="shop" placeholder="your-shop-url.myshopify.com">
|
35
|
+
<button type="submit">Log In</button>
|
36
|
+
</form>
|
37
|
+
```
|
38
|
+
|
39
|
+
Or without form `/auth/shopify?shop=your-shop-url.myshopify.com`
|
40
|
+
Alternatively you can put shop parameter to session as [Shopify App](https://github.com/Shopify/shopify_app) do
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
session['shopify.omniauth_params'] = { shop: params[:shop] }
|
44
|
+
```
|
45
|
+
|
46
|
+
And finally it's possible to use your own query parameter by overriding default setup method. For example, like below:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
50
|
+
provider :shopify,
|
51
|
+
ENV['SHOPIFY_API_KEY'],
|
52
|
+
ENV['SHOPIFY_SHARED_SECRET'],
|
53
|
+
option :setup, proc { |env|
|
54
|
+
strategy = env['omniauth.strategy']
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
site = if strategy.request.params['site']
|
59
|
+
"https://#{strategy.request.params['site']}"
|
60
|
+
else
|
61
|
+
''
|
62
|
+
end
|
63
|
+
|
64
|
+
env['omniauth.strategy'].options[:client_options][:site] = site
|
65
|
+
}
|
66
|
+
```
|
67
|
+
|
29
68
|
## Configuring
|
30
69
|
|
70
|
+
### Scope
|
71
|
+
|
31
72
|
You can configure the scope, which you pass in to the `provider` method via a `Hash`:
|
32
73
|
|
33
74
|
* `scope`: A comma-separated list of permissions you want to request from the user. See [the Shopify API docs](http://docs.shopify.com/api/tutorials/oauth) for a full list of available permissions.
|
@@ -40,6 +81,19 @@ Rails.application.config.middleware.use OmniAuth::Builder do
|
|
40
81
|
end
|
41
82
|
```
|
42
83
|
|
84
|
+
### Online Access
|
85
|
+
|
86
|
+
Shopify offers two different types of access tokens: [online access and offline access](https://help.shopify.com/api/getting-started/authentication/oauth/api-access-modes). You can configure for online-access by passing the `per_user_permissions` option:
|
87
|
+
|
88
|
+
```
|
89
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
90
|
+
provider :shopify, ENV['SHOPIFY_API_KEY'],
|
91
|
+
ENV['SHOPIFY_SHARED_SECRET'],
|
92
|
+
:scope => 'read_orders',
|
93
|
+
:per_user_permissions => true
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
43
97
|
## Authentication Hash
|
44
98
|
|
45
99
|
Here's an example *Authentication Hash* available in `request.env['omniauth.auth']`:
|
data/SECURITY.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported versions
|
4
|
+
|
5
|
+
### New features
|
6
|
+
|
7
|
+
New features will only be added to the master branch and will not be made available in point releases.
|
8
|
+
|
9
|
+
### Bug fixes
|
10
|
+
|
11
|
+
Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from.
|
12
|
+
|
13
|
+
### Security issues
|
14
|
+
|
15
|
+
Only the latest release series will receive patches and new versions in case of a security issue.
|
16
|
+
|
17
|
+
### Severe security issues
|
18
|
+
|
19
|
+
For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team.
|
20
|
+
|
21
|
+
### Unsupported Release Series
|
22
|
+
|
23
|
+
When a release series is no longer supported, it's your own responsibility to deal with bugs and security issues. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
|
24
|
+
|
25
|
+
## Reporting a bug
|
26
|
+
|
27
|
+
All security bugs in shopify repositories should be reported to [our hackerone program](https://hackerone.com/shopify)
|
28
|
+
Shopify's whitehat program is our way to reward security researchers for finding serious security vulnerabilities in the In Scope properties listed at the bottom of this page, including our core application (all functionality associated with a Shopify store, particularly your-store.myshopify.com/admin) and certain ancillary applications.
|
29
|
+
|
30
|
+
## Disclosure Policy
|
31
|
+
|
32
|
+
We look forward to working with all security researchers and strive to be respectful, always assume the best and treat others as peers. We expect the same in return from all participants. To achieve this, our team strives to:
|
33
|
+
|
34
|
+
- Reply to all reports within one business day and triage within two business days (if applicable)
|
35
|
+
- Be as transparent as possible, answering all inquires about our report decisions and adding hackers to duplicate HackerOne reports
|
36
|
+
- Award bounties within a week of resolution (excluding extenuating circumstances)
|
37
|
+
- Only close reports as N/A when the issue reported is included in Known Issues, Ineligible Vulnerabilities Types or lacks evidence of a vulnerability
|
38
|
+
|
39
|
+
**The following rules must be followed in order for any rewards to be paid:**
|
40
|
+
|
41
|
+
- You may only test against shops you have created which include your HackerOne YOURHANDLE @ wearehackerone.com registered email address.
|
42
|
+
- You must not attempt to gain access to, or interact with, any shops other than those created by you.
|
43
|
+
- The use of commercial scanners is prohibited (e.g., Nessus).
|
44
|
+
- Rules for reporting must be followed.
|
45
|
+
- Do not disclose any issues publicly before they have been resolved.
|
46
|
+
- Shopify reserves the right to modify the rules for this program or deem any submissions invalid at any time. Shopify may cancel the whitehat program without notice at any time.
|
47
|
+
- Contacting Shopify Support over chat, email or phone about your HackerOne report is not allowed. We may disqualify you from receiving a reward, or from participating in the program altogether.
|
48
|
+
- You are not an employee of Shopify; employees should report bugs to the internal bug bounty program.
|
49
|
+
- You hereby represent, warrant and covenant that any content you submit to Shopify is an original work of authorship and that you are legally entitled to grant the rights and privileges conveyed by these terms. You further represent, warrant and covenant that the consent of no other person or entity is or will be necessary for Shopify to use the submitted content.
|
50
|
+
- By submitting content to Shopify, you irrevocably waive all moral rights which you may have in the content.
|
51
|
+
- All content submitted by you to Shopify under this program is licensed under the MIT License.
|
52
|
+
- You must report any discovered vulnerability to Shopify as soon as you have validated the vulnerability.
|
53
|
+
- Failure to follow any of the foregoing rules will disqualify you from participating in this program.
|
54
|
+
|
55
|
+
** Please see our [Hackerone Profile](https://hackerone.com/shopify) for full details
|
56
|
+
|
57
|
+
## Receiving Security Updates
|
58
|
+
|
59
|
+
To recieve all general updates to vulnerabilities, please subscribe to our hackerone [Hacktivity](https://hackerone.com/shopify/hacktivity)
|
data/example/config.ru
CHANGED
@@ -17,18 +17,42 @@ module OmniAuth
|
|
17
17
|
|
18
18
|
option :callback_url
|
19
19
|
option :myshopify_domain, 'myshopify.com'
|
20
|
+
option :old_client_secret
|
20
21
|
|
21
|
-
# When `true`, the
|
22
|
-
#
|
23
|
-
option :
|
22
|
+
# When `true`, the user's permission level will apply (in addition to
|
23
|
+
# the requested access scope) when making API requests to Shopify.
|
24
|
+
option :per_user_permissions, false
|
24
25
|
|
25
26
|
option :setup, proc { |env|
|
26
|
-
|
27
|
-
|
27
|
+
strategy = env['omniauth.strategy']
|
28
|
+
|
29
|
+
shopify_auth_params = strategy.session['shopify.omniauth_params'] ||
|
30
|
+
strategy.session['omniauth.params'] ||
|
31
|
+
strategy.request.params
|
32
|
+
|
33
|
+
shopify_auth_params = shopify_auth_params && shopify_auth_params.with_indifferent_access
|
34
|
+
shop = if shopify_auth_params && shopify_auth_params['shop']
|
35
|
+
"https://#{shopify_auth_params['shop']}"
|
36
|
+
else
|
37
|
+
''
|
38
|
+
end
|
39
|
+
|
40
|
+
strategy.options[:client_options][:site] = shop
|
28
41
|
}
|
29
42
|
|
30
43
|
uid { URI.parse(options[:client_options][:site]).host }
|
31
44
|
|
45
|
+
extra do
|
46
|
+
if access_token
|
47
|
+
{
|
48
|
+
'associated_user' => access_token['associated_user'],
|
49
|
+
'associated_user_scope' => access_token['associated_user_scope'],
|
50
|
+
'scope' => access_token['scope'],
|
51
|
+
'session' => access_token['session']
|
52
|
+
}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
32
56
|
def valid_site?
|
33
57
|
!!(/\A(https|http)\:\/\/[a-zA-Z0-9][a-zA-Z0-9\-]*\.#{Regexp.quote(options[:myshopify_domain])}[\/]?\z/ =~ options[:client_options][:site])
|
34
58
|
end
|
@@ -43,8 +67,10 @@ module OmniAuth
|
|
43
67
|
|
44
68
|
return false unless timestamp.to_i > Time.now.to_i - CODE_EXPIRES_AFTER
|
45
69
|
|
46
|
-
|
47
|
-
|
70
|
+
new_secret = options.client_secret
|
71
|
+
old_secret = options.old_client_secret
|
72
|
+
|
73
|
+
validate_signature(new_secret) || (old_secret && validate_signature(old_secret))
|
48
74
|
end
|
49
75
|
|
50
76
|
def valid_scope?(token)
|
@@ -56,7 +82,7 @@ module OmniAuth
|
|
56
82
|
|
57
83
|
def normalized_scopes(scopes)
|
58
84
|
scope_list = scopes.to_s.split(SCOPE_DELIMITER).map(&:strip).reject(&:empty?).uniq
|
59
|
-
ignore_scopes = scope_list.map { |scope| scope =~ /\
|
85
|
+
ignore_scopes = scope_list.map { |scope| scope =~ /\A(unauthenticated_)?write_(.*)\z/ && "#{$1}read_#{$2}" }.compact
|
60
86
|
scope_list - ignore_scopes
|
61
87
|
end
|
62
88
|
|
@@ -64,15 +90,24 @@ module OmniAuth
|
|
64
90
|
params = params.dup
|
65
91
|
params.delete('hmac')
|
66
92
|
params.delete('signature') # deprecated signature
|
67
|
-
|
93
|
+
Rack::Utils.build_query(params.sort)
|
68
94
|
end
|
69
95
|
|
70
96
|
def self.hmac_sign(encoded_params, secret)
|
71
97
|
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, secret, encoded_params)
|
72
98
|
end
|
73
99
|
|
100
|
+
def valid_permissions?(token)
|
101
|
+
return false unless token
|
102
|
+
|
103
|
+
return true if options[:per_user_permissions] && token['associated_user']
|
104
|
+
return true if !options[:per_user_permissions] && !token['associated_user']
|
105
|
+
|
106
|
+
false
|
107
|
+
end
|
108
|
+
|
74
109
|
def fix_https
|
75
|
-
options[:client_options][:site].gsub
|
110
|
+
options[:client_options][:site] = options[:client_options][:site].gsub(/\Ahttp\:/, 'https:')
|
76
111
|
end
|
77
112
|
|
78
113
|
def setup_phase
|
@@ -96,8 +131,13 @@ module OmniAuth
|
|
96
131
|
unless valid_scope?(token)
|
97
132
|
return fail!(:invalid_scope, CallbackError.new(:invalid_scope, "Scope does not match, it may have been tampered with."))
|
98
133
|
end
|
134
|
+
unless valid_permissions?(token)
|
135
|
+
return fail!(:invalid_permissions, CallbackError.new(:invalid_permissions, "Requested API access mode does not match."))
|
136
|
+
end
|
99
137
|
|
100
138
|
super
|
139
|
+
rescue ::OAuth2::Error => e
|
140
|
+
fail!(:invalid_credentials, e)
|
101
141
|
end
|
102
142
|
|
103
143
|
def build_access_token
|
@@ -107,12 +147,21 @@ module OmniAuth
|
|
107
147
|
def authorize_params
|
108
148
|
super.tap do |params|
|
109
149
|
params[:scope] = normalized_scopes(params[:scope] || DEFAULT_SCOPE).join(SCOPE_DELIMITER)
|
150
|
+
params[:grant_options] = ['per-user'] if options[:per_user_permissions]
|
110
151
|
end
|
111
152
|
end
|
112
153
|
|
113
154
|
def callback_url
|
114
155
|
options[:callback_url] || full_host + script_name + callback_path
|
115
156
|
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def validate_signature(secret)
|
161
|
+
params = request.GET
|
162
|
+
calculated_signature = self.class.hmac_sign(self.class.encoded_params_for_signature(params), secret)
|
163
|
+
Rack::Utils.secure_compare(calculated_signature, params['hmac'])
|
164
|
+
end
|
116
165
|
end
|
117
166
|
end
|
118
167
|
end
|
@@ -6,19 +6,24 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = 'omniauth-shopify-oauth2'
|
7
7
|
s.version = OmniAuth::Shopify::VERSION
|
8
8
|
s.authors = ['Denis Odorcic']
|
9
|
-
s.email = ['
|
9
|
+
s.email = ['gems@shopify.com']
|
10
10
|
s.summary = 'Shopify strategy for OmniAuth'
|
11
11
|
s.homepage = 'https://github.com/Shopify/omniauth-shopify-oauth2'
|
12
12
|
s.license = 'MIT'
|
13
13
|
|
14
|
+
s.metadata['allowed_push_host'] = 'https://rubygems.org'
|
15
|
+
|
14
16
|
s.files = `git ls-files`.split("\n")
|
15
17
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
17
19
|
s.require_paths = ['lib']
|
20
|
+
s.required_ruby_version = '>= 2.1.9'
|
18
21
|
|
19
|
-
s.add_runtime_dependency 'omniauth-oauth2', '~> 1.
|
22
|
+
s.add_runtime_dependency 'omniauth-oauth2', '~> 1.5'
|
23
|
+
s.add_runtime_dependency 'activesupport'
|
20
24
|
|
21
25
|
s.add_development_dependency 'minitest', '~> 5.6'
|
26
|
+
s.add_development_dependency 'rspec', '~> 3.9.0'
|
22
27
|
s.add_development_dependency 'fakeweb', '~> 1.3'
|
23
28
|
s.add_development_dependency 'rake'
|
24
29
|
end
|
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'spec_helper'
|
2
1
|
require 'omniauth-shopify-oauth2'
|
3
2
|
require 'base64'
|
4
3
|
|
@@ -29,6 +28,12 @@ describe OmniAuth::Strategies::Shopify do
|
|
29
28
|
subject.options[:client_options][:site].should eq('https://foo.bar/')
|
30
29
|
end
|
31
30
|
|
31
|
+
it 'replaces http scheme by https with an immutable string' do
|
32
|
+
@options = {:client_options => {:site => 'http://foo.bar/'.freeze}}
|
33
|
+
subject.fix_https
|
34
|
+
subject.options[:client_options][:site].should eq('https://foo.bar/')
|
35
|
+
end
|
36
|
+
|
32
37
|
it 'does not replace https scheme' do
|
33
38
|
@options = {:client_options => {:site => 'https://foo.bar/'}}
|
34
39
|
subject.fix_https
|
@@ -135,4 +140,80 @@ describe OmniAuth::Strategies::Shopify do
|
|
135
140
|
subject.valid_site?.should eq(true)
|
136
141
|
end
|
137
142
|
end
|
143
|
+
|
144
|
+
describe '#valid_permissions?' do
|
145
|
+
let(:associated_user) do
|
146
|
+
{}
|
147
|
+
end
|
148
|
+
|
149
|
+
let(:token) do
|
150
|
+
{
|
151
|
+
'associated_user' => associated_user,
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'returns false if there is no token' do
|
156
|
+
expect(subject.valid_permissions?(nil)).to be_falsey
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'with per_user_permissions is present' do
|
160
|
+
before do
|
161
|
+
@options = @options.merge(per_user_permissions: true)
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'when token does not have associated user' do
|
165
|
+
let(:associated_user) { nil }
|
166
|
+
|
167
|
+
it 'return false' do
|
168
|
+
expect(subject.valid_permissions?(token)).to be_falsey
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'when token has associated user' do
|
173
|
+
it 'return true' do
|
174
|
+
expect(subject.valid_permissions?(token)).to be_truthy
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context 'with per_user_permissions is false' do
|
180
|
+
before do
|
181
|
+
@options = @options.merge(per_user_permissions: false)
|
182
|
+
end
|
183
|
+
|
184
|
+
context 'when token does not have associated user' do
|
185
|
+
let(:associated_user) { nil }
|
186
|
+
|
187
|
+
it 'return true' do
|
188
|
+
expect(subject.valid_permissions?(token)).to be_truthy
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'when token has associated user' do
|
193
|
+
it 'return false' do
|
194
|
+
expect(subject.valid_permissions?(token)).to be_falsey
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'with per_user_permissions is nil' do
|
200
|
+
before do
|
201
|
+
@options = @options.merge(per_user_permissions: nil)
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'when token does not have associated user' do
|
205
|
+
let(:associated_user) { nil }
|
206
|
+
|
207
|
+
it 'return true' do
|
208
|
+
expect(subject.valid_permissions?(token)).to be_truthy
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'when token has associated user' do
|
213
|
+
it 'return false' do
|
214
|
+
expect(subject.valid_permissions?(token)).to be_falsey
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
138
219
|
end
|
data/test/integration_test.rb
CHANGED
@@ -13,11 +13,19 @@ class IntegrationTest < Minitest::Test
|
|
13
13
|
def test_authorize
|
14
14
|
response = authorize('snowdevil.myshopify.com')
|
15
15
|
assert_equal 302, response.status
|
16
|
-
assert_match
|
16
|
+
assert_match %r{\A#{Regexp.quote(shopify_authorize_url)}}, response.location
|
17
17
|
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
18
18
|
assert_equal "123", redirect_params['client_id']
|
19
19
|
assert_equal "https://app.example.com/auth/shopify/callback", redirect_params['redirect_uri']
|
20
20
|
assert_equal "read_products", redirect_params['scope']
|
21
|
+
assert_nil redirect_params['grant_options']
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_authorize_includes_auth_type_when_per_user_permissions_are_requested
|
25
|
+
build_app(per_user_permissions: true)
|
26
|
+
response = authorize('snowdevil.myshopify.com')
|
27
|
+
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
28
|
+
assert_equal 'per-user', redirect_params['grant_options[]']
|
21
29
|
end
|
22
30
|
|
23
31
|
def test_authorize_overrides_site_with_https_scheme
|
@@ -26,8 +34,8 @@ class IntegrationTest < Minitest::Test
|
|
26
34
|
env['omniauth.strategy'].options[:client_options][:site] = "http://#{params['shop']}"
|
27
35
|
}
|
28
36
|
|
29
|
-
response =
|
30
|
-
assert_match
|
37
|
+
response = request.get('https://app.example.com/auth/shopify?shop=snowdevil.myshopify.com')
|
38
|
+
assert_match %r{\A#{Regexp.quote(shopify_authorize_url)}}, response.location
|
31
39
|
end
|
32
40
|
|
33
41
|
def test_site_validation
|
@@ -40,10 +48,11 @@ class IntegrationTest < Minitest::Test
|
|
40
48
|
'user@snowdevil.myshopify.com', # shop contains user
|
41
49
|
'snowdevil.myshopify.com:22', # shop contains port
|
42
50
|
].each do |shop, valid|
|
51
|
+
@shop = shop
|
43
52
|
response = authorize(shop)
|
44
53
|
assert_auth_failure(response, 'invalid_site')
|
45
54
|
|
46
|
-
response = callback(
|
55
|
+
response = callback(sign_with_new_secret(shop: shop, code: code))
|
47
56
|
assert_auth_failure(response, 'invalid_site')
|
48
57
|
end
|
49
58
|
end
|
@@ -53,7 +62,7 @@ class IntegrationTest < Minitest::Test
|
|
53
62
|
code = SecureRandom.hex(16)
|
54
63
|
expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
|
55
64
|
|
56
|
-
response = callback(
|
65
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
57
66
|
|
58
67
|
assert_callback_success(response, access_token, code)
|
59
68
|
end
|
@@ -64,7 +73,7 @@ class IntegrationTest < Minitest::Test
|
|
64
73
|
code = SecureRandom.hex(16)
|
65
74
|
expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
|
66
75
|
|
67
|
-
response = callback(
|
76
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]).merge(signature: 'ignored'))
|
68
77
|
|
69
78
|
assert_callback_success(response, access_token, code)
|
70
79
|
end
|
@@ -77,7 +86,7 @@ class IntegrationTest < Minitest::Test
|
|
77
86
|
|
78
87
|
now = Time.now.to_i
|
79
88
|
params = { shop: 'snowdevil.myshopify.com', code: code, timestamp: now, next: '/products?page=2&q=red%20shirt', state: opts["rack.session"]["omniauth.state"] }
|
80
|
-
encoded_params = "code=#{code}&next
|
89
|
+
encoded_params = "code=#{code}&next=%2Fproducts%3Fpage%3D2%26q%3Dred%2520shirt&shop=snowdevil.myshopify.com&state=#{opts["rack.session"]["omniauth.state"]}×tamp=#{now}"
|
81
90
|
params[:hmac] = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @secret, encoded_params)
|
82
91
|
|
83
92
|
response = callback(params)
|
@@ -91,21 +100,21 @@ class IntegrationTest < Minitest::Test
|
|
91
100
|
code = SecureRandom.hex(16)
|
92
101
|
expect_access_token_request(access_token, 'read_orders,write_products')
|
93
102
|
|
94
|
-
response = callback(
|
103
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
95
104
|
|
96
105
|
assert_callback_success(response, access_token, code)
|
97
106
|
end
|
98
107
|
|
99
108
|
def test_callback_rejects_invalid_hmac
|
100
109
|
@secret = 'wrong_secret'
|
101
|
-
response = callback(
|
110
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16)))
|
102
111
|
|
103
112
|
assert_auth_failure(response, 'invalid_signature')
|
104
113
|
end
|
105
114
|
|
106
115
|
def test_callback_rejects_old_timestamps
|
107
116
|
expired_timestamp = Time.now.to_i - OmniAuth::Strategies::Shopify::CODE_EXPIRES_AFTER - 1
|
108
|
-
response = callback(
|
117
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16), timestamp: expired_timestamp))
|
109
118
|
|
110
119
|
assert_auth_failure(response, 'invalid_signature')
|
111
120
|
end
|
@@ -120,12 +129,15 @@ class IntegrationTest < Minitest::Test
|
|
120
129
|
|
121
130
|
def test_callback_rejects_body_params
|
122
131
|
code = SecureRandom.hex(16)
|
123
|
-
params =
|
132
|
+
params = sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code)
|
124
133
|
body = Rack::Utils.build_nested_query(unsigned: 'value')
|
125
134
|
|
126
135
|
response = request.get("https://app.example.com/auth/shopify/callback?#{Rack::Utils.build_query(params)}",
|
127
136
|
input: body,
|
128
|
-
"CONTENT_TYPE" => 'application/x-www-form-urlencoded'
|
137
|
+
"CONTENT_TYPE" => 'application/x-www-form-urlencoded',
|
138
|
+
'rack.session' => {
|
139
|
+
'shopify.omniauth_params' => { shop: 'snowdevil.myshopify.com' }
|
140
|
+
})
|
129
141
|
|
130
142
|
assert_auth_failure(response, 'invalid_signature')
|
131
143
|
end
|
@@ -140,28 +152,47 @@ class IntegrationTest < Minitest::Test
|
|
140
152
|
env['omniauth.strategy'].options[:client_options][:site] = "https://#{shop}"
|
141
153
|
}
|
142
154
|
|
143
|
-
response =
|
155
|
+
response = request.get("https://app.example.com/auth/shopify?shop=snowdevil.myshopify.dev:3000")
|
144
156
|
assert_equal 302, response.status
|
145
|
-
assert_match
|
157
|
+
assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.dev:3000/admin/oauth/authorize?")}}, response.location
|
146
158
|
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
147
159
|
assert_equal 'read_products,read_orders,write_content', redirect_params['scope']
|
148
160
|
assert_equal 'https://app.example.com/admin/auth/legacy/callback', redirect_params['redirect_uri']
|
149
161
|
end
|
150
162
|
|
163
|
+
def test_default_setup_reads_shop_from_session
|
164
|
+
build_app
|
165
|
+
response = authorize('snowdevil.myshopify.com')
|
166
|
+
assert_equal 302, response.status
|
167
|
+
assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}}, response.location
|
168
|
+
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
169
|
+
assert_equal 'https://app.example.com/auth/shopify/callback', redirect_params['redirect_uri']
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_default_setup_reads_shop_from_params
|
173
|
+
build_app
|
174
|
+
|
175
|
+
response = request.get('https://app.example.com/auth/shopify?shop=snowdevil.myshopify.com', opts)
|
176
|
+
|
177
|
+
assert_equal 302, response.status
|
178
|
+
assert_match %r{\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}}, response.location
|
179
|
+
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
180
|
+
assert_equal 'https://app.example.com/auth/shopify/callback', redirect_params['redirect_uri']
|
181
|
+
end
|
182
|
+
|
151
183
|
def test_unnecessary_read_scopes_are_removed
|
152
|
-
build_app scope: 'read_content,read_products,write_products',
|
184
|
+
build_app scope: 'read_content,read_products,write_products,unauthenticated_read_checkouts,unauthenticated_write_checkouts',
|
153
185
|
callback_path: '/admin/auth/legacy/callback',
|
154
186
|
myshopify_domain: 'myshopify.dev:3000',
|
155
187
|
setup: lambda { |env|
|
156
188
|
shop = Rack::Request.new(env).GET['shop']
|
157
|
-
shop += ".myshopify.dev:3000" unless shop.include?(".")
|
158
189
|
env['omniauth.strategy'].options[:client_options][:site] = "https://#{shop}"
|
159
190
|
}
|
160
191
|
|
161
|
-
response =
|
192
|
+
response = request.get("https://app.example.com/auth/shopify?shop=snowdevil.myshopify.dev:3000")
|
162
193
|
assert_equal 302, response.status
|
163
194
|
redirect_params = Rack::Utils.parse_query(URI(response.location).query)
|
164
|
-
assert_equal 'read_content,write_products', redirect_params['scope']
|
195
|
+
assert_equal 'read_content,write_products,unauthenticated_write_checkouts', redirect_params['scope']
|
165
196
|
end
|
166
197
|
|
167
198
|
def test_callback_with_invalid_state_fails
|
@@ -169,7 +200,7 @@ class IntegrationTest < Minitest::Test
|
|
169
200
|
code = SecureRandom.hex(16)
|
170
201
|
expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
|
171
202
|
|
172
|
-
response = callback(
|
203
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: 'invalid'))
|
173
204
|
|
174
205
|
assert_equal 302, response.status
|
175
206
|
assert_equal '/auth/failure?message=csrf_detected&strategy=shopify', response.location
|
@@ -178,9 +209,9 @@ class IntegrationTest < Minitest::Test
|
|
178
209
|
def test_callback_with_mismatching_scope_fails
|
179
210
|
access_token = SecureRandom.hex(16)
|
180
211
|
code = SecureRandom.hex(16)
|
181
|
-
expect_access_token_request(access_token, 'some_invalid_scope')
|
212
|
+
expect_access_token_request(access_token, 'some_invalid_scope', nil)
|
182
213
|
|
183
|
-
response = callback(
|
214
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
184
215
|
|
185
216
|
assert_equal 302, response.status
|
186
217
|
assert_equal '/auth/failure?message=invalid_scope&strategy=shopify', response.location
|
@@ -191,7 +222,7 @@ class IntegrationTest < Minitest::Test
|
|
191
222
|
code = SecureRandom.hex(16)
|
192
223
|
expect_access_token_request(access_token, nil)
|
193
224
|
|
194
|
-
response = callback(
|
225
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
195
226
|
|
196
227
|
assert_equal 302, response.status
|
197
228
|
assert_equal '/auth/failure?message=invalid_scope&strategy=shopify', response.location
|
@@ -204,7 +235,7 @@ class IntegrationTest < Minitest::Test
|
|
204
235
|
code = SecureRandom.hex(16)
|
205
236
|
expect_access_token_request(access_token, 'first_scope')
|
206
237
|
|
207
|
-
response = callback(
|
238
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
208
239
|
|
209
240
|
assert_equal 302, response.status
|
210
241
|
assert_equal '/auth/failure?message=invalid_scope&strategy=shopify', response.location
|
@@ -217,7 +248,7 @@ class IntegrationTest < Minitest::Test
|
|
217
248
|
code = SecureRandom.hex(16)
|
218
249
|
expect_access_token_request(access_token, 'second_scope,first_scope,third_scope')
|
219
250
|
|
220
|
-
response = callback(
|
251
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
221
252
|
|
222
253
|
assert_equal 302, response.status
|
223
254
|
assert_equal '/auth/failure?message=invalid_scope&strategy=shopify', response.location
|
@@ -230,7 +261,19 @@ class IntegrationTest < Minitest::Test
|
|
230
261
|
code = SecureRandom.hex(16)
|
231
262
|
expect_access_token_request(access_token, 'second_scope,first_scope')
|
232
263
|
|
233
|
-
response = callback(
|
264
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
265
|
+
|
266
|
+
assert_callback_success(response, access_token, code)
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_callback_with_duplicate_read_scopes_works
|
270
|
+
build_app scope: 'read_products,write_products,unauthenticated_read_products,unauthenticated_write_products'
|
271
|
+
|
272
|
+
access_token = SecureRandom.hex(16)
|
273
|
+
code = SecureRandom.hex(16)
|
274
|
+
expect_access_token_request(access_token, 'write_products,unauthenticated_write_products')
|
275
|
+
|
276
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
234
277
|
|
235
278
|
assert_callback_success(response, access_token, code)
|
236
279
|
end
|
@@ -242,26 +285,143 @@ class IntegrationTest < Minitest::Test
|
|
242
285
|
code = SecureRandom.hex(16)
|
243
286
|
expect_access_token_request(access_token, 'read_content,write_products')
|
244
287
|
|
245
|
-
response = callback(
|
288
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
289
|
+
|
290
|
+
assert_callback_success(response, access_token, code)
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_callback_when_per_user_permissions_are_present_but_not_requested
|
294
|
+
build_app(scope: 'scope', per_user_permissions: false)
|
295
|
+
|
296
|
+
access_token = SecureRandom.hex(16)
|
297
|
+
code = SecureRandom.hex(16)
|
298
|
+
expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'})
|
299
|
+
|
300
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
301
|
+
|
302
|
+
assert_equal 302, response.status
|
303
|
+
assert_equal '/auth/failure?message=invalid_permissions&strategy=shopify', response.location
|
304
|
+
end
|
305
|
+
|
306
|
+
def test_callback_when_per_user_permissions_are_not_present_and_options_is_nil
|
307
|
+
build_app(scope: 'scope', per_user_permissions: nil)
|
308
|
+
|
309
|
+
access_token = SecureRandom.hex(16)
|
310
|
+
code = SecureRandom.hex(16)
|
311
|
+
expect_access_token_request(access_token, 'scope', nil)
|
312
|
+
|
313
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
314
|
+
|
315
|
+
assert_callback_success(response, access_token, code)
|
316
|
+
end
|
317
|
+
|
318
|
+
def test_callback_when_per_user_permissions_are_not_present_but_requested
|
319
|
+
build_app(scope: 'scope', per_user_permissions: true)
|
320
|
+
|
321
|
+
access_token = SecureRandom.hex(16)
|
322
|
+
code = SecureRandom.hex(16)
|
323
|
+
expect_access_token_request(access_token, 'scope', nil)
|
324
|
+
|
325
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
326
|
+
|
327
|
+
assert_equal 302, response.status
|
328
|
+
assert_equal '/auth/failure?message=invalid_permissions&strategy=shopify', response.location
|
329
|
+
end
|
330
|
+
|
331
|
+
def test_callback_works_when_per_user_permissions_are_present_and_requested
|
332
|
+
build_app(scope: 'scope', per_user_permissions: true)
|
333
|
+
|
334
|
+
access_token = SecureRandom.hex(16)
|
335
|
+
code = SecureRandom.hex(16)
|
336
|
+
expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'})
|
337
|
+
|
338
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
339
|
+
|
340
|
+
assert_equal 200, response.status
|
341
|
+
end
|
342
|
+
|
343
|
+
def test_callback_when_a_session_is_present
|
344
|
+
build_app(scope: 'scope', per_user_permissions: true)
|
345
|
+
|
346
|
+
access_token = SecureRandom.hex(16)
|
347
|
+
code = SecureRandom.hex(16)
|
348
|
+
session = SecureRandom.hex
|
349
|
+
expect_access_token_request(access_token, 'scope', { id: 1, email: 'bob@bobsen.com'}, session)
|
350
|
+
|
351
|
+
response = callback(sign_with_new_secret(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
|
352
|
+
|
353
|
+
assert_equal 200, response.status
|
354
|
+
end
|
355
|
+
|
356
|
+
def test_callback_works_with_old_secret
|
357
|
+
build_app scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE
|
358
|
+
access_token = SecureRandom.hex(16)
|
359
|
+
code = SecureRandom.hex(16)
|
360
|
+
expect_access_token_request(access_token, OmniAuth::Strategies::Shopify::DEFAULT_SCOPE)
|
361
|
+
|
362
|
+
signed_params = sign_with_old_secret(
|
363
|
+
shop: 'snowdevil.myshopify.com',
|
364
|
+
code: code,
|
365
|
+
state: opts["rack.session"]["omniauth.state"]
|
366
|
+
)
|
367
|
+
|
368
|
+
response = callback(signed_params)
|
246
369
|
|
247
370
|
assert_callback_success(response, access_token, code)
|
248
371
|
end
|
249
372
|
|
373
|
+
def test_callback_when_creds_are_invalid
|
374
|
+
build_app scope: OmniAuth::Strategies::Shopify::DEFAULT_SCOPE
|
375
|
+
|
376
|
+
FakeWeb.register_uri(
|
377
|
+
:post,
|
378
|
+
"https://snowdevil.myshopify.com/admin/oauth/access_token",
|
379
|
+
status: [ "401", "Invalid token" ],
|
380
|
+
body: "Token is invalid or has already been requested"
|
381
|
+
)
|
382
|
+
|
383
|
+
signed_params = sign_with_new_secret(
|
384
|
+
shop: 'snowdevil.myshopify.com',
|
385
|
+
code: SecureRandom.hex(16),
|
386
|
+
state: opts["rack.session"]["omniauth.state"]
|
387
|
+
)
|
388
|
+
|
389
|
+
response = callback(signed_params)
|
390
|
+
|
391
|
+
assert_equal 302, response.status
|
392
|
+
assert_equal '/auth/failure?message=invalid_credentials&strategy=shopify', response.location
|
393
|
+
end
|
394
|
+
|
250
395
|
private
|
251
396
|
|
252
|
-
def
|
253
|
-
params = params
|
397
|
+
def sign_with_old_secret(params)
|
398
|
+
params = add_time(params)
|
399
|
+
encoded_params = OmniAuth::Strategies::Shopify.encoded_params_for_signature(params)
|
400
|
+
params['hmac'] = OmniAuth::Strategies::Shopify.hmac_sign(encoded_params, @old_secret)
|
401
|
+
params
|
402
|
+
end
|
254
403
|
|
404
|
+
def add_time(params)
|
405
|
+
params = params.dup
|
255
406
|
params[:timestamp] ||= Time.now.to_i
|
407
|
+
params
|
408
|
+
end
|
256
409
|
|
410
|
+
def sign_with_new_secret(params)
|
411
|
+
params = add_time(params)
|
257
412
|
encoded_params = OmniAuth::Strategies::Shopify.encoded_params_for_signature(params)
|
258
413
|
params['hmac'] = OmniAuth::Strategies::Shopify.hmac_sign(encoded_params, @secret)
|
259
414
|
params
|
260
415
|
end
|
261
416
|
|
262
|
-
def expect_access_token_request(access_token, scope)
|
417
|
+
def expect_access_token_request(access_token, scope, associated_user=nil, session=nil)
|
263
418
|
FakeWeb.register_uri(:post, "https://snowdevil.myshopify.com/admin/oauth/access_token",
|
264
|
-
body: JSON.dump(
|
419
|
+
body: JSON.dump(
|
420
|
+
access_token: access_token,
|
421
|
+
scope: scope,
|
422
|
+
associated_user: associated_user,
|
423
|
+
session: session,
|
424
|
+
),
|
265
425
|
content_type: 'application/json')
|
266
426
|
end
|
267
427
|
|
@@ -282,10 +442,13 @@ class IntegrationTest < Minitest::Test
|
|
282
442
|
def assert_auth_failure(response, reason)
|
283
443
|
assert_nil FakeWeb.last_request
|
284
444
|
assert_equal 302, response.status
|
285
|
-
assert_match
|
445
|
+
assert_match %r{\A#{Regexp.quote("/auth/failure?message=#{reason}")}}, response.location
|
286
446
|
end
|
287
447
|
|
288
448
|
def build_app(options={})
|
449
|
+
@old_secret = '12d34s1'
|
450
|
+
@secret = '53cr3tz'
|
451
|
+
options.merge!(old_client_secret: @old_secret)
|
289
452
|
app = proc { |env|
|
290
453
|
@omniauth_result = env['omniauth.auth']
|
291
454
|
[200, {Rack::CONTENT_TYPE => "text/plain"}, "OK"]
|
@@ -293,17 +456,22 @@ class IntegrationTest < Minitest::Test
|
|
293
456
|
|
294
457
|
opts["rack.session"]["omniauth.state"] = SecureRandom.hex(32)
|
295
458
|
app = OmniAuth::Builder.new(app) do
|
296
|
-
provider :shopify, '123', '53cr3tz', options
|
459
|
+
provider :shopify, '123', '53cr3tz' , options
|
297
460
|
end
|
298
|
-
@secret = '53cr3tz'
|
299
461
|
@app = Rack::Session::Cookie.new(app, secret: SecureRandom.hex(64))
|
300
462
|
end
|
301
463
|
|
464
|
+
def shop
|
465
|
+
@shop ||= 'snowdevil.myshopify.com'
|
466
|
+
end
|
467
|
+
|
302
468
|
def authorize(shop)
|
303
|
-
|
469
|
+
@opts['rack.session']['shopify.omniauth_params'] = { shop: shop }
|
470
|
+
request.get('https://app.example.com/auth/shopify', opts)
|
304
471
|
end
|
305
472
|
|
306
473
|
def callback(params)
|
474
|
+
@opts['rack.session']['shopify.omniauth_params'] = { shop: shop }
|
307
475
|
request.get("https://app.example.com/auth/shopify/callback?#{Rack::Utils.build_query(params)}", opts)
|
308
476
|
end
|
309
477
|
|
@@ -314,4 +482,8 @@ class IntegrationTest < Minitest::Test
|
|
314
482
|
def request
|
315
483
|
Rack::MockRequest.new(@app)
|
316
484
|
end
|
485
|
+
|
486
|
+
def shopify_authorize_url
|
487
|
+
"https://snowdevil.myshopify.com/admin/oauth/authorize?"
|
488
|
+
end
|
317
489
|
end
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-shopify-oauth2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Odorcic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: omniauth-oauth2
|
@@ -16,14 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '1.
|
19
|
+
version: '1.5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '1.
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: minitest
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '5.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.9.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.9.0
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: fakeweb
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,16 +96,18 @@ dependencies:
|
|
68
96
|
version: '0'
|
69
97
|
description:
|
70
98
|
email:
|
71
|
-
-
|
99
|
+
- gems@shopify.com
|
72
100
|
executables: []
|
73
101
|
extensions: []
|
74
102
|
extra_rdoc_files: []
|
75
103
|
files:
|
104
|
+
- ".github/probots.yml"
|
105
|
+
- ".github/workflows/build.yml"
|
76
106
|
- ".gitignore"
|
77
|
-
- ".travis.yml"
|
78
107
|
- Gemfile
|
79
108
|
- README.md
|
80
109
|
- Rakefile
|
110
|
+
- SECURITY.md
|
81
111
|
- example/Gemfile
|
82
112
|
- example/config.ru
|
83
113
|
- lib/omniauth-shopify-oauth2.rb
|
@@ -92,7 +122,8 @@ files:
|
|
92
122
|
homepage: https://github.com/Shopify/omniauth-shopify-oauth2
|
93
123
|
licenses:
|
94
124
|
- MIT
|
95
|
-
metadata:
|
125
|
+
metadata:
|
126
|
+
allowed_push_host: https://rubygems.org
|
96
127
|
post_install_message:
|
97
128
|
rdoc_options: []
|
98
129
|
require_paths:
|
@@ -101,15 +132,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
132
|
requirements:
|
102
133
|
- - ">="
|
103
134
|
- !ruby/object:Gem::Version
|
104
|
-
version:
|
135
|
+
version: 2.1.9
|
105
136
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
137
|
requirements:
|
107
138
|
- - ">="
|
108
139
|
- !ruby/object:Gem::Version
|
109
140
|
version: '0'
|
110
141
|
requirements: []
|
111
|
-
|
112
|
-
rubygems_version: 2.2.3
|
142
|
+
rubygems_version: 3.0.3
|
113
143
|
signing_key:
|
114
144
|
specification_version: 4
|
115
145
|
summary: Shopify strategy for OmniAuth
|
data/.travis.yml
DELETED