stripe 2.8.0 → 2.9.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
2
  SHA1:
3
- metadata.gz: 747667dd050969e755253ea2c5328142ebff0dd2
4
- data.tar.gz: f60cfc0497a8b0044811283e893a667755f3f6cd
3
+ metadata.gz: 0f9ea5d1ce3e948a439878fadba81ef52b47c24e
4
+ data.tar.gz: 00d62fe82daf1570ada3004672ddd15b6fe2425b
5
5
  SHA512:
6
- metadata.gz: b405457d2c0615be9e9fdf1ea20229b95365fb0a615d2f1b508fe0b4466c8842e7a7469317208e477280df0eb51d280e1e82928d5465de54fdca7e786df44faa
7
- data.tar.gz: 8718ad60ec44fd3af67bfb5b8567c56a35141ce14937f6a88ed7d455084ed491cb93a3d2fb45c31d2297d0324667ba8d43517aea65b87c72aea7c28240ed9863
6
+ metadata.gz: 227094e5bb975bbd0acaf044d5f3708055fb8c12f043af80dd4851a8a784646f2f506d45c4a87ea166cbae285085623eb7aebb1366a1f5dec035a95437fede56
7
+ data.tar.gz: 3158ef959bc7d5ed39bc26b9482515b3c8aec2fdf0d96d6f6a87c2481d254517688e994d6b6ea8fd7006a70d809f42af8ab76eb0caa621e6ec1f69bf47a85c9b
@@ -1,3 +1,7 @@
1
+ === 2.9.0 2017-05-18
2
+
3
+ * Support for OAuth operations in `Stripe::OAuth`
4
+
1
5
  === 2.8.0 2017-04-28
2
6
 
3
7
  * Support for checking webhook signatures
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.8.0
1
+ 2.9.0
@@ -69,6 +69,9 @@ require 'stripe/three_d_secure'
69
69
  require 'stripe/token'
70
70
  require 'stripe/transfer'
71
71
 
72
+ # OAuth
73
+ require 'stripe/oauth'
74
+
72
75
  module Stripe
73
76
  DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
74
77
 
@@ -90,7 +93,7 @@ module Stripe
90
93
  @read_timeout = 80
91
94
 
92
95
  class << self
93
- attr_accessor :stripe_account, :api_key, :api_base, :verify_ssl_certs, :api_version, :connect_base, :uploads_base,
96
+ attr_accessor :stripe_account, :api_key, :api_base, :verify_ssl_certs, :api_version, :client_id, :connect_base, :uploads_base,
94
97
  :open_timeout, :read_timeout
95
98
 
96
99
  attr_reader :max_network_retry_delay, :initial_network_retry_delay
@@ -93,11 +93,12 @@ module Stripe
93
93
  raise NoMethodError.new('Overridding legal_entity can cause serious issues. Instead, set the individual fields of legal_entity like blah.legal_entity.first_name = \'Blah\'')
94
94
  end
95
95
 
96
- def deauthorize(client_id, opts={})
97
- opts = {:api_base => Stripe.connect_base}.merge(Util.normalize_opts(opts))
98
- resp, opts = request(:post, '/oauth/deauthorize', { 'client_id' => client_id, 'stripe_user_id' => self.id }, opts)
99
- opts.delete(:api_base) # the api_base here is a one-off, don't persist it
100
- Util.convert_to_stripe_object(resp.data, opts)
96
+ def deauthorize(client_id=nil, opts={})
97
+ params = {
98
+ client_id: client_id,
99
+ stripe_user_id: self.id,
100
+ }
101
+ OAuth.deauthorize(params, opts)
101
102
  end
102
103
 
103
104
  ARGUMENT_NOT_PROVIDED = Object.new
@@ -100,4 +100,44 @@ module Stripe
100
100
  @sig_header = sig_header
101
101
  end
102
102
  end
103
+
104
+ module OAuth
105
+ # OAuthError is raised when the OAuth API returns an error.
106
+ class OAuthError < StripeError
107
+ attr_accessor :code
108
+
109
+ def initialize(code, description, http_status: nil, http_body: nil, json_body: nil,
110
+ http_headers: nil)
111
+ super(description, http_status: http_status, http_body: http_body,
112
+ json_body: json_body, http_headers: http_headers)
113
+ @code = code
114
+ end
115
+ end
116
+
117
+ # InvalidGrantError is raised when a specified code doesn't exist, is
118
+ # expired, has been used, or doesn't belong to you; a refresh token doesn't
119
+ # exist, or doesn't belong to you; or if an API key's mode (live or test)
120
+ # doesn't match the mode of a code or refresh token.
121
+ class InvalidGrantError < OAuthError
122
+ end
123
+
124
+ # InvalidRequestError is raised when a code, refresh token, or grant type
125
+ # parameter is not provided, but was required.
126
+ class InvalidRequestError < OAuthError
127
+ end
128
+
129
+ # InvalidScopeError is raised when an invalid scope parameter is provided.
130
+ class InvalidScopeError < OAuthError
131
+ end
132
+
133
+ # UnsupportedGrantTypeError is raised when an unuspported grant type
134
+ # parameter is specified.
135
+ class UnsupportedGrantTypeError < OAuthError
136
+ end
137
+
138
+ # UnsupportedResponseTypeError is raised when an unsupported response type
139
+ # parameter is specified.
140
+ class UnsupportedResponseTypeError < OAuthError
141
+ end
142
+ end
103
143
  end
@@ -0,0 +1,56 @@
1
+ module Stripe
2
+ module OAuth
3
+ module OAuthOperations
4
+ extend APIOperations::Request::ClassMethods
5
+
6
+ def self.request(method, url, params, opts)
7
+ opts = Util.normalize_opts(opts)
8
+ opts[:client] ||= StripeClient.active_client
9
+ opts[:api_base] ||= Stripe.connect_base
10
+
11
+ super(method, url, params, opts)
12
+ end
13
+ end
14
+
15
+ def self.get_client_id(params={})
16
+ client_id = params[:client_id] || Stripe.client_id
17
+ unless client_id
18
+ raise AuthenticationError.new('No client_id provided. ' \
19
+ 'Set your client_id using "Stripe.client_id = <CLIENT-ID>". ' \
20
+ 'You can find your client_ids in your Stripe dashboard at ' \
21
+ 'https://dashboard.stripe.com/account/applications/settings, ' \
22
+ 'after registering your account as a platform. See ' \
23
+ 'https://stripe.com/docs/connect/standalone-accounts for details, ' \
24
+ 'or email support@stripe.com if you have any questions.')
25
+ end
26
+ client_id
27
+ end
28
+
29
+ def self.authorize_url(params={}, opts={})
30
+ base = opts[:connect_base] || Stripe.connect_base
31
+
32
+ params[:client_id] = get_client_id(params)
33
+ params[:response_type] ||= 'code'
34
+ query = Util.encode_parameters(params)
35
+
36
+ "#{base}/oauth/authorize?#{query}"
37
+ end
38
+
39
+ def self.token(params={}, opts={})
40
+ opts = Util.normalize_opts(opts)
41
+ resp, opts = OAuthOperations.request(
42
+ :post, '/oauth/token', params, opts)
43
+ # This is just going to return a generic StripeObject, but that's okay
44
+ Util.convert_to_stripe_object(resp.data, opts)
45
+ end
46
+
47
+ def self.deauthorize(params={}, opts={})
48
+ opts = Util.normalize_opts(opts)
49
+ params[:client_id] = get_client_id(params)
50
+ resp, opts = OAuthOperations.request(
51
+ :post, '/oauth/deauthorize', params, opts)
52
+ # This is just going to return a generic StripeObject, but that's okay
53
+ Util.convert_to_stripe_object(resp.data, opts)
54
+ end
55
+ end
56
+ end
@@ -197,7 +197,7 @@ module Stripe
197
197
  case e
198
198
  when Faraday::ClientError
199
199
  if e.response
200
- handle_api_error(e.response)
200
+ handle_error_response(e.response)
201
201
  else
202
202
  handle_network_error(e, retry_count, api_base)
203
203
  end
@@ -229,12 +229,12 @@ module Stripe
229
229
  str
230
230
  end
231
231
 
232
- def handle_api_error(http_resp)
232
+ def handle_error_response(http_resp)
233
233
  begin
234
234
  resp = StripeResponse.from_faraday_hash(http_resp)
235
- error = resp.data[:error]
235
+ error_data = resp.data[:error]
236
236
 
237
- unless error && error.is_a?(Hash)
237
+ unless error_data
238
238
  raise StripeError.new("Indeterminate error")
239
239
  end
240
240
 
@@ -242,47 +242,79 @@ module Stripe
242
242
  raise general_api_error(http_resp[:status], http_resp[:body])
243
243
  end
244
244
 
245
+ if error_data.is_a?(String)
246
+ error = specific_oauth_error(resp, error_data)
247
+ end
248
+ if error.nil?
249
+ error = specific_api_error(resp, error_data)
250
+ end
251
+
252
+ error.response = resp
253
+ raise(error)
254
+ end
255
+
256
+ def specific_api_error(resp, error_data)
245
257
  case resp.http_status
246
258
  when 400, 404
247
259
  error = InvalidRequestError.new(
248
- error[:message], error[:param],
260
+ error_data[:message], error_data[:param],
249
261
  http_status: resp.http_status, http_body: resp.http_body,
250
262
  json_body: resp.data, http_headers: resp.http_headers
251
263
  )
252
264
  when 401
253
265
  error = AuthenticationError.new(
254
- error[:message],
266
+ error_data[:message],
255
267
  http_status: resp.http_status, http_body: resp.http_body,
256
268
  json_body: resp.data, http_headers: resp.http_headers
257
269
  )
258
270
  when 402
259
271
  error = CardError.new(
260
- error[:message], error[:param], error[:code],
272
+ error_data[:message], error_data[:param], error_data[:code],
261
273
  http_status: resp.http_status, http_body: resp.http_body,
262
274
  json_body: resp.data, http_headers: resp.http_headers
263
275
  )
264
276
  when 403
265
277
  error = PermissionError.new(
266
- error[:message],
278
+ error_data[:message],
267
279
  http_status: resp.http_status, http_body: resp.http_body,
268
280
  json_body: resp.data, http_headers: resp.http_headers
269
281
  )
270
282
  when 429
271
283
  error = RateLimitError.new(
272
- error[:message],
284
+ error_data[:message],
273
285
  http_status: resp.http_status, http_body: resp.http_body,
274
286
  json_body: resp.data, http_headers: resp.http_headers
275
287
  )
276
288
  else
277
289
  error = APIError.new(
278
- error[:message],
290
+ error_data[:message],
279
291
  http_status: resp.http_status, http_body: resp.http_body,
280
292
  json_body: resp.data, http_headers: resp.http_headers
281
293
  )
282
294
  end
283
295
 
284
- error.response = resp
285
- raise(error)
296
+ error
297
+ end
298
+
299
+ # Attempts to look at a response's error code and return an OAuth error if
300
+ # one matches. Will return `nil` if the code isn't recognized.
301
+ def specific_oauth_error(resp, error_code)
302
+ description = resp.data[:error_description] || error_code
303
+
304
+ args = [error_code, description, {
305
+ http_status: resp.http_status, http_body: resp.http_body,
306
+ json_body: resp.data, http_headers: resp.http_headers
307
+ }]
308
+
309
+ case error_code
310
+ when 'invalid_grant' then OAuth::InvalidGrantError.new(*args)
311
+ when 'invalid_request' then OAuth::InvalidRequestError.new(*args)
312
+ when 'invalid_scope' then OAuth::InvalidScopeError.new(*args)
313
+ when 'unsupported_grant_type' then OAuth::UnsupportedGrantTypeError.new(*args)
314
+ when 'unsupported_response_type' then OAuth::UnsupportedResponseTypeError.new(*args)
315
+ else
316
+ nil
317
+ end
286
318
  end
287
319
 
288
320
  def handle_network_error(e, retry_count, api_base=nil)
@@ -1,3 +1,3 @@
1
1
  module Stripe
2
- VERSION = '2.8.0'
2
+ VERSION = '2.9.0'
3
3
  end
@@ -0,0 +1,85 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ module Stripe
4
+ class OAuthTest < Test::Unit::TestCase
5
+ setup do
6
+ Stripe.client_id = 'ca_test'
7
+ end
8
+
9
+ teardown do
10
+ Stripe.client_id = nil
11
+ end
12
+
13
+ context ".authorize_url" do
14
+ should "return the authorize URL" do
15
+ uri_str = OAuth.authorize_url({
16
+ scope: 'read_write',
17
+ state: 'csrf_token',
18
+ stripe_user: {
19
+ email: 'test@example.com',
20
+ url: 'https://example.com/profile/test',
21
+ country: 'US',
22
+ },
23
+ })
24
+
25
+ uri = URI::parse(uri_str)
26
+ params = CGI::parse(uri.query)
27
+
28
+ assert_equal('https', uri.scheme)
29
+ assert_equal('connect.stripe.com', uri.host)
30
+ assert_equal('/oauth/authorize', uri.path)
31
+
32
+ assert_equal(['ca_test'], params['client_id'])
33
+ assert_equal(['read_write'], params['scope'])
34
+ assert_equal(['test@example.com'], params['stripe_user[email]'])
35
+ assert_equal(['https://example.com/profile/test'], params['stripe_user[url]'])
36
+ assert_equal(['US'], params['stripe_user[country]'])
37
+ end
38
+ end
39
+
40
+ context ".token" do
41
+ should "exchange a code for an access token" do
42
+ # The OpenAPI fixtures don't cover the OAuth endpoints, so we just
43
+ # stub the request manually.
44
+ stub_request(:post, "#{Stripe.connect_base}/oauth/token").
45
+ with(body: {
46
+ 'grant_type' => 'authorization_code',
47
+ 'code' => 'this_is_an_authorization_code',
48
+ }).
49
+ to_return(body: JSON.generate({
50
+ access_token: 'sk_access_token',
51
+ scope: 'read_only',
52
+ livemode: false,
53
+ token_type: 'bearer',
54
+ refresh_token: 'sk_refresh_token',
55
+ stripe_user_id: 'acct_test',
56
+ stripe_publishable_key: 'pk_test',
57
+ }))
58
+
59
+ resp = OAuth.token({
60
+ grant_type: 'authorization_code',
61
+ code: 'this_is_an_authorization_code',
62
+ })
63
+ assert_equal('sk_access_token', resp.access_token)
64
+ end
65
+ end
66
+
67
+ context ".deauthorize" do
68
+ should "deauthorize an account" do
69
+ # The OpenAPI fixtures don't cover the OAuth endpoints, so we just
70
+ # stub the request manually.
71
+ stub_request(:post, "#{Stripe.connect_base}/oauth/deauthorize").
72
+ with(body: {
73
+ 'client_id' => 'ca_test',
74
+ 'stripe_user_id' => 'acct_test_deauth',
75
+ }).
76
+ to_return(body: JSON.generate({
77
+ stripe_user_id: 'acct_test_deauth',
78
+ }))
79
+
80
+ resp = OAuth.deauthorize({stripe_user_id: 'acct_test_deauth'})
81
+ assert_equal('acct_test_deauth', resp.stripe_user_id)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -243,16 +243,16 @@ module Stripe
243
243
  assert_equal 'Invalid response object from API: "" (HTTP response code was 200)', e.message
244
244
  end
245
245
 
246
- should "handle error response with non-object error value" do
246
+ should "handle error response with unknown value" do
247
247
  stub_request(:post, "#{Stripe.api_base}/v1/charges").
248
- to_return(body: JSON.generate({ error: "foo" }), status: 500)
248
+ to_return(body: JSON.generate({ bar: "foo" }), status: 500)
249
249
 
250
250
  client = StripeClient.new
251
251
  e = assert_raises Stripe::APIError do
252
252
  client.execute_request(:post, '/v1/charges')
253
253
  end
254
254
 
255
- assert_equal 'Invalid response object from API: "{\"error\":\"foo\"}" (HTTP response code was 500)', e.message
255
+ assert_equal 'Invalid response object from API: "{\"bar\":\"foo\"}" (HTTP response code was 500)', e.message
256
256
  end
257
257
 
258
258
  should "raise InvalidRequestError on 400" do
@@ -332,6 +332,42 @@ module Stripe
332
332
  assert_equal(true, e.json_body.kind_of?(Hash))
333
333
  end
334
334
  end
335
+
336
+ should "raise OAuth::InvalidRequestError when error is a string with value 'invalid_request'" do
337
+ stub_request(:post, "#{Stripe.connect_base}/oauth/token").
338
+ to_return(body: JSON.generate({
339
+ error: "invalid_request",
340
+ error_description: "No grant type specified",
341
+ }), status: 400)
342
+
343
+ client = StripeClient.new
344
+ opts = {api_base: Stripe.connect_base}
345
+ e = assert_raises Stripe::OAuth::InvalidRequestError do
346
+ client.execute_request(:post, '/oauth/token', opts)
347
+ end
348
+
349
+ assert_equal(400, e.http_status)
350
+ assert_equal(true, !!e.http_body)
351
+ assert_equal('No grant type specified', e.message)
352
+ end
353
+
354
+ should "raise OAuth::InvalidGrantError when error is a string with value 'invalid_grant'" do
355
+ stub_request(:post, "#{Stripe.connect_base}/oauth/token").
356
+ to_return(body: JSON.generate({
357
+ error: "invalid_grant",
358
+ error_description: "This authorization code has already been used. All tokens issued with this code have been revoked.",
359
+ }), status: 400)
360
+
361
+ client = StripeClient.new
362
+ opts = {api_base: Stripe.connect_base}
363
+ e = assert_raises Stripe::OAuth::InvalidGrantError do
364
+ client.execute_request(:post, '/oauth/token', opts)
365
+ end
366
+
367
+ assert_equal(400, e.http_status)
368
+ assert_equal('invalid_grant', e.code)
369
+ assert_equal('This authorization code has already been used. All tokens issued with this code have been revoked.', e.message)
370
+ end
335
371
  end
336
372
 
337
373
  context "idempotency keys" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stripe
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stripe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-28 00:00:00.000000000 Z
11
+ date: 2017-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -75,6 +75,7 @@ files:
75
75
  - lib/stripe/invoice_item.rb
76
76
  - lib/stripe/invoice_line_item.rb
77
77
  - lib/stripe/list_object.rb
78
+ - lib/stripe/oauth.rb
78
79
  - lib/stripe/order.rb
79
80
  - lib/stripe/order_return.rb
80
81
  - lib/stripe/payout.rb
@@ -128,6 +129,7 @@ files:
128
129
  - test/stripe/invoice_line_item_test.rb
129
130
  - test/stripe/invoice_test.rb
130
131
  - test/stripe/list_object_test.rb
132
+ - test/stripe/oauth_test.rb
131
133
  - test/stripe/order_return_test.rb
132
134
  - test/stripe/order_test.rb
133
135
  - test/stripe/payout_test.rb
@@ -201,6 +203,7 @@ test_files:
201
203
  - test/stripe/invoice_line_item_test.rb
202
204
  - test/stripe/invoice_test.rb
203
205
  - test/stripe/list_object_test.rb
206
+ - test/stripe/oauth_test.rb
204
207
  - test/stripe/order_return_test.rb
205
208
  - test/stripe/order_test.rb
206
209
  - test/stripe/payout_test.rb