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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e88a21bce303e6c6d637d58c50a2bfa7d75dfe86
4
- data.tar.gz: bced14c2fffa27c843425fe95dd088dbb5866c28
2
+ SHA256:
3
+ metadata.gz: 0031b4b2dbba2b3b01f7805026f5a1a1043dd154192102244563ad248090f0ce
4
+ data.tar.gz: 3fd9935fe819c0bc35813d08c0c8e41baa1de43e8333fa3c1f7c494e068336c1
5
5
  SHA512:
6
- metadata.gz: 575588b874c8c8d1774070ed66fbf55e8d70f9b82f9097c6f97e1ccb3423e792b82b400c2c3e481527eb60f5f54a69092aebd5715a5211845033bdc3a3be974c
7
- data.tar.gz: 3aa7bb0263d3c262f6c93aa39ce8729d92225fe712c9f0acc46560aed468f8c9dc125b4820a20273add6d4490b424215ba89565ac8c9fd8488d74dcc4dbe0f17
6
+ metadata.gz: 819569da31d85ea93405929aab51022a4fdfdb998524de8f39d616bfaace0bac71d193b82414c4b04dcad06f0fcc81081079a6d1e10b82a4509eeed81c99888e
7
+ data.tar.gz: 86a9888309a4d6e46f80d56455469160a056aeb961beefc5aefc9f2ff0df42689cb4d349bac3212219093554aa534a841fd50127ab4af6faa7952dc4413194b8
@@ -0,0 +1,2 @@
1
+ enabled:
2
+ - cla
@@ -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
@@ -1,3 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
+
5
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2")
6
+ gem 'rack', '~> 1.6'
7
+ end
8
+
9
+ group :development, :test do
10
+ gem 'fakeweb', git: 'https://github.com/chrisk/fakeweb.git'
11
+ end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://api.travis-ci.org/Shopify/omniauth-shopify-oauth2.png?branch=master)](http://travis-ci.org/Shopify/omniauth-shopify-oauth2)
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
@@ -1,5 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  require 'sinatra/base'
3
+ require 'active_support/core_ext/hash'
3
4
  require 'omniauth-shopify-oauth2'
4
5
 
5
6
  SCOPE = 'read_products,read_orders,read_customers,write_shipping'
@@ -1,5 +1,5 @@
1
1
  module OmniAuth
2
2
  module Shopify
3
- VERSION = "1.1.14"
3
+ VERSION = "2.3.0"
4
4
  end
5
5
  end
@@ -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 authorization phase will fail if the granted scopes
22
- # mismatch the requested scopes.
23
- option :validate_granted_scopes, true
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
- request = Rack::Request.new(env)
27
- env['omniauth.strategy'].options[:client_options][:site] = "https://#{request.GET['shop']}"
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
- calculated_signature = self.class.hmac_sign(self.class.encoded_params_for_signature(params), options.client_secret)
47
- Rack::Utils.secure_compare(calculated_signature, signature)
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 =~ /\Awrite_(.*)\z/ && "read_#{$1}" }.compact
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
- params.map{|k,v| "#{URI.escape(k.to_s, '&=%')}=#{URI.escape(v.to_s, '&%')}"}.sort.join('&')
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!(/\Ahttp\:/, 'https:')
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 = ['denis.odorcic@shopify.com']
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.2'
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
@@ -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 /\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}/, response.location
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 = authorize('snowdevil.myshopify.com')
30
- assert_match /\A#{Regexp.quote("https://snowdevil.myshopify.com/admin/oauth/authorize?")}/, response.location
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(sign_params(shop: shop, code: code))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]).merge(signature: 'ignored'))
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=/products?page=2%26q=red%2520shirt&shop=snowdevil.myshopify.com&state=#{opts["rack.session"]["omniauth.state"]}&timestamp=#{now}"
89
+ encoded_params = "code=#{code}&next=%2Fproducts%3Fpage%3D2%26q%3Dred%2520shirt&shop=snowdevil.myshopify.com&state=#{opts["rack.session"]["omniauth.state"]}&timestamp=#{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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16)))
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(sign_params(shop: 'snowdevil.myshopify.com', code: SecureRandom.hex(16), timestamp: expired_timestamp))
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 = sign_params(shop: 'snowdevil.myshopify.com', code: code)
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 = authorize('snowdevil')
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 /\A#{Regexp.quote("https://snowdevil.myshopify.dev:3000/admin/oauth/authorize?")}/, response.location
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 = authorize('snowdevil')
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: 'invalid'))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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(sign_params(shop: 'snowdevil.myshopify.com', code: code, state: opts["rack.session"]["omniauth.state"]))
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 sign_params(params)
253
- params = params.dup
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(access_token: access_token, scope: scope),
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 /\A#{Regexp.quote("/auth/failure?message=#{reason}")}/, response.location
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
- request.get("https://app.example.com/auth/shopify?shop=#{CGI.escape(shop)}", opts)
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
@@ -5,6 +5,7 @@ require 'omniauth-shopify-oauth2'
5
5
  require 'minitest/autorun'
6
6
  require 'fakeweb'
7
7
  require 'json'
8
+ require 'active_support/core_ext/hash'
8
9
 
9
10
  OmniAuth.config.logger = Logger.new(nil)
10
11
  FakeWeb.allow_net_connect = false
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: 1.1.14
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: 2016-02-25 00:00:00.000000000 Z
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.2'
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.2'
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
- - denis.odorcic@shopify.com
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: '0'
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
- rubyforge_project:
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
@@ -1,4 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.1.0
4
- - 2.2.2