mirror of
https://github.com/stripe/stripe-ruby.git
synced 2025-05-31 00:00:37 -04:00
commit
429afa959f
@ -69,6 +69,9 @@ require 'stripe/three_d_secure'
|
||||
require 'stripe/token'
|
||||
require 'stripe/transfer'
|
||||
|
||||
# OAuth
|
||||
require 'stripe/oauth'
|
||||
|
||||
module Stripe
|
||||
DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
|
||||
|
||||
@ -90,7 +93,7 @@ module Stripe
|
||||
@read_timeout = 80
|
||||
|
||||
class << self
|
||||
attr_accessor :stripe_account, :api_key, :api_base, :verify_ssl_certs, :api_version, :connect_base, :uploads_base,
|
||||
attr_accessor :stripe_account, :api_key, :api_base, :verify_ssl_certs, :api_version, :client_id, :connect_base, :uploads_base,
|
||||
:open_timeout, :read_timeout
|
||||
|
||||
attr_reader :max_network_retry_delay, :initial_network_retry_delay
|
||||
|
@ -93,11 +93,12 @@ module Stripe
|
||||
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\'')
|
||||
end
|
||||
|
||||
def deauthorize(client_id, opts={})
|
||||
opts = {:api_base => Stripe.connect_base}.merge(Util.normalize_opts(opts))
|
||||
resp, opts = request(:post, '/oauth/deauthorize', { 'client_id' => client_id, 'stripe_user_id' => self.id }, opts)
|
||||
opts.delete(:api_base) # the api_base here is a one-off, don't persist it
|
||||
Util.convert_to_stripe_object(resp.data, opts)
|
||||
def deauthorize(client_id=nil, opts={})
|
||||
params = {
|
||||
client_id: client_id,
|
||||
stripe_user_id: self.id,
|
||||
}
|
||||
OAuth.deauthorize(params, opts)
|
||||
end
|
||||
|
||||
ARGUMENT_NOT_PROVIDED = Object.new
|
||||
|
@ -100,4 +100,44 @@ module Stripe
|
||||
@sig_header = sig_header
|
||||
end
|
||||
end
|
||||
|
||||
module OAuth
|
||||
# OAuthError is raised when the OAuth API returns an error.
|
||||
class OAuthError < StripeError
|
||||
attr_accessor :code
|
||||
|
||||
def initialize(code, description, http_status: nil, http_body: nil, json_body: nil,
|
||||
http_headers: nil)
|
||||
super(description, http_status: http_status, http_body: http_body,
|
||||
json_body: json_body, http_headers: http_headers)
|
||||
@code = code
|
||||
end
|
||||
end
|
||||
|
||||
# InvalidGrantError is raised when a specified code doesn't exist, is
|
||||
# expired, has been used, or doesn't belong to you; a refresh token doesn't
|
||||
# exist, or doesn't belong to you; or if an API key's mode (live or test)
|
||||
# doesn't match the mode of a code or refresh token.
|
||||
class InvalidGrantError < OAuthError
|
||||
end
|
||||
|
||||
# InvalidRequestError is raised when a code, refresh token, or grant type
|
||||
# parameter is not provided, but was required.
|
||||
class InvalidRequestError < OAuthError
|
||||
end
|
||||
|
||||
# InvalidScopeError is raised when an invalid scope parameter is provided.
|
||||
class InvalidScopeError < OAuthError
|
||||
end
|
||||
|
||||
# UnsupportedGrantTypeError is raised when an unuspported grant type
|
||||
# parameter is specified.
|
||||
class UnsupportedGrantTypeError < OAuthError
|
||||
end
|
||||
|
||||
# UnsupportedResponseTypeError is raised when an unsupported response type
|
||||
# parameter is specified.
|
||||
class UnsupportedResponseTypeError < OAuthError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
56
lib/stripe/oauth.rb
Normal file
56
lib/stripe/oauth.rb
Normal file
@ -0,0 +1,56 @@
|
||||
module Stripe
|
||||
module OAuth
|
||||
module OAuthOperations
|
||||
extend APIOperations::Request::ClassMethods
|
||||
|
||||
def self.request(method, url, params, opts)
|
||||
opts = Util.normalize_opts(opts)
|
||||
opts[:client] ||= StripeClient.active_client
|
||||
opts[:api_base] ||= Stripe.connect_base
|
||||
|
||||
super(method, url, params, opts)
|
||||
end
|
||||
end
|
||||
|
||||
def self.get_client_id(params={})
|
||||
client_id = params[:client_id] || Stripe.client_id
|
||||
unless client_id
|
||||
raise AuthenticationError.new('No client_id provided. ' \
|
||||
'Set your client_id using "Stripe.client_id = <CLIENT-ID>". ' \
|
||||
'You can find your client_ids in your Stripe dashboard at ' \
|
||||
'https://dashboard.stripe.com/account/applications/settings, ' \
|
||||
'after registering your account as a platform. See ' \
|
||||
'https://stripe.com/docs/connect/standalone-accounts for details, ' \
|
||||
'or email support@stripe.com if you have any questions.')
|
||||
end
|
||||
client_id
|
||||
end
|
||||
|
||||
def self.authorize_url(params={}, opts={})
|
||||
base = opts[:connect_base] || Stripe.connect_base
|
||||
|
||||
params[:client_id] = get_client_id(params)
|
||||
params[:response_type] ||= 'code'
|
||||
query = Util.encode_parameters(params)
|
||||
|
||||
"#{base}/oauth/authorize?#{query}"
|
||||
end
|
||||
|
||||
def self.token(params={}, opts={})
|
||||
opts = Util.normalize_opts(opts)
|
||||
resp, opts = OAuthOperations.request(
|
||||
:post, '/oauth/token', params, opts)
|
||||
# This is just going to return a generic StripeObject, but that's okay
|
||||
Util.convert_to_stripe_object(resp.data, opts)
|
||||
end
|
||||
|
||||
def self.deauthorize(params={}, opts={})
|
||||
opts = Util.normalize_opts(opts)
|
||||
params[:client_id] = get_client_id(params)
|
||||
resp, opts = OAuthOperations.request(
|
||||
:post, '/oauth/deauthorize', params, opts)
|
||||
# This is just going to return a generic StripeObject, but that's okay
|
||||
Util.convert_to_stripe_object(resp.data, opts)
|
||||
end
|
||||
end
|
||||
end
|
@ -197,7 +197,7 @@ module Stripe
|
||||
case e
|
||||
when Faraday::ClientError
|
||||
if e.response
|
||||
handle_api_error(e.response)
|
||||
handle_error_response(e.response)
|
||||
else
|
||||
handle_network_error(e, retry_count, api_base)
|
||||
end
|
||||
@ -229,12 +229,12 @@ module Stripe
|
||||
str
|
||||
end
|
||||
|
||||
def handle_api_error(http_resp)
|
||||
def handle_error_response(http_resp)
|
||||
begin
|
||||
resp = StripeResponse.from_faraday_hash(http_resp)
|
||||
error = resp.data[:error]
|
||||
error_data = resp.data[:error]
|
||||
|
||||
unless error && error.is_a?(Hash)
|
||||
unless error_data
|
||||
raise StripeError.new("Indeterminate error")
|
||||
end
|
||||
|
||||
@ -242,47 +242,79 @@ module Stripe
|
||||
raise general_api_error(http_resp[:status], http_resp[:body])
|
||||
end
|
||||
|
||||
if error_data.is_a?(String)
|
||||
error = specific_oauth_error(resp, error_data)
|
||||
end
|
||||
if error.nil?
|
||||
error = specific_api_error(resp, error_data)
|
||||
end
|
||||
|
||||
error.response = resp
|
||||
raise(error)
|
||||
end
|
||||
|
||||
def specific_api_error(resp, error_data)
|
||||
case resp.http_status
|
||||
when 400, 404
|
||||
error = InvalidRequestError.new(
|
||||
error[:message], error[:param],
|
||||
error_data[:message], error_data[:param],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
when 401
|
||||
error = AuthenticationError.new(
|
||||
error[:message],
|
||||
error_data[:message],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
when 402
|
||||
error = CardError.new(
|
||||
error[:message], error[:param], error[:code],
|
||||
error_data[:message], error_data[:param], error_data[:code],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
when 403
|
||||
error = PermissionError.new(
|
||||
error[:message],
|
||||
error_data[:message],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
when 429
|
||||
error = RateLimitError.new(
|
||||
error[:message],
|
||||
error_data[:message],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
else
|
||||
error = APIError.new(
|
||||
error[:message],
|
||||
error_data[:message],
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
)
|
||||
end
|
||||
|
||||
error.response = resp
|
||||
raise(error)
|
||||
error
|
||||
end
|
||||
|
||||
# Attempts to look at a response's error code and return an OAuth error if
|
||||
# one matches. Will return `nil` if the code isn't recognized.
|
||||
def specific_oauth_error(resp, error_code)
|
||||
description = resp.data[:error_description] || error_code
|
||||
|
||||
args = [error_code, description, {
|
||||
http_status: resp.http_status, http_body: resp.http_body,
|
||||
json_body: resp.data, http_headers: resp.http_headers
|
||||
}]
|
||||
|
||||
case error_code
|
||||
when 'invalid_grant' then OAuth::InvalidGrantError.new(*args)
|
||||
when 'invalid_request' then OAuth::InvalidRequestError.new(*args)
|
||||
when 'invalid_scope' then OAuth::InvalidScopeError.new(*args)
|
||||
when 'unsupported_grant_type' then OAuth::UnsupportedGrantTypeError.new(*args)
|
||||
when 'unsupported_response_type' then OAuth::UnsupportedResponseTypeError.new(*args)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def handle_network_error(e, retry_count, api_base=nil)
|
||||
|
85
test/stripe/oauth_test.rb
Normal file
85
test/stripe/oauth_test.rb
Normal file
@ -0,0 +1,85 @@
|
||||
require File.expand_path('../../test_helper', __FILE__)
|
||||
|
||||
module Stripe
|
||||
class OAuthTest < Test::Unit::TestCase
|
||||
setup do
|
||||
Stripe.client_id = 'ca_test'
|
||||
end
|
||||
|
||||
teardown do
|
||||
Stripe.client_id = nil
|
||||
end
|
||||
|
||||
context ".authorize_url" do
|
||||
should "return the authorize URL" do
|
||||
uri_str = OAuth.authorize_url({
|
||||
scope: 'read_write',
|
||||
state: 'csrf_token',
|
||||
stripe_user: {
|
||||
email: 'test@example.com',
|
||||
url: 'https://example.com/profile/test',
|
||||
country: 'US',
|
||||
},
|
||||
})
|
||||
|
||||
uri = URI::parse(uri_str)
|
||||
params = CGI::parse(uri.query)
|
||||
|
||||
assert_equal('https', uri.scheme)
|
||||
assert_equal('connect.stripe.com', uri.host)
|
||||
assert_equal('/oauth/authorize', uri.path)
|
||||
|
||||
assert_equal(['ca_test'], params['client_id'])
|
||||
assert_equal(['read_write'], params['scope'])
|
||||
assert_equal(['test@example.com'], params['stripe_user[email]'])
|
||||
assert_equal(['https://example.com/profile/test'], params['stripe_user[url]'])
|
||||
assert_equal(['US'], params['stripe_user[country]'])
|
||||
end
|
||||
end
|
||||
|
||||
context ".token" do
|
||||
should "exchange a code for an access token" do
|
||||
# The OpenAPI fixtures don't cover the OAuth endpoints, so we just
|
||||
# stub the request manually.
|
||||
stub_request(:post, "#{Stripe.connect_base}/oauth/token").
|
||||
with(body: {
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => 'this_is_an_authorization_code',
|
||||
}).
|
||||
to_return(body: JSON.generate({
|
||||
access_token: 'sk_access_token',
|
||||
scope: 'read_only',
|
||||
livemode: false,
|
||||
token_type: 'bearer',
|
||||
refresh_token: 'sk_refresh_token',
|
||||
stripe_user_id: 'acct_test',
|
||||
stripe_publishable_key: 'pk_test',
|
||||
}))
|
||||
|
||||
resp = OAuth.token({
|
||||
grant_type: 'authorization_code',
|
||||
code: 'this_is_an_authorization_code',
|
||||
})
|
||||
assert_equal('sk_access_token', resp.access_token)
|
||||
end
|
||||
end
|
||||
|
||||
context ".deauthorize" do
|
||||
should "deauthorize an account" do
|
||||
# The OpenAPI fixtures don't cover the OAuth endpoints, so we just
|
||||
# stub the request manually.
|
||||
stub_request(:post, "#{Stripe.connect_base}/oauth/deauthorize").
|
||||
with(body: {
|
||||
'client_id' => 'ca_test',
|
||||
'stripe_user_id' => 'acct_test_deauth',
|
||||
}).
|
||||
to_return(body: JSON.generate({
|
||||
stripe_user_id: 'acct_test_deauth',
|
||||
}))
|
||||
|
||||
resp = OAuth.deauthorize({stripe_user_id: 'acct_test_deauth'})
|
||||
assert_equal('acct_test_deauth', resp.stripe_user_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -243,16 +243,16 @@ module Stripe
|
||||
assert_equal 'Invalid response object from API: "" (HTTP response code was 200)', e.message
|
||||
end
|
||||
|
||||
should "handle error response with non-object error value" do
|
||||
should "handle error response with unknown value" do
|
||||
stub_request(:post, "#{Stripe.api_base}/v1/charges").
|
||||
to_return(body: JSON.generate({ error: "foo" }), status: 500)
|
||||
to_return(body: JSON.generate({ bar: "foo" }), status: 500)
|
||||
|
||||
client = StripeClient.new
|
||||
e = assert_raises Stripe::APIError do
|
||||
client.execute_request(:post, '/v1/charges')
|
||||
end
|
||||
|
||||
assert_equal 'Invalid response object from API: "{\"error\":\"foo\"}" (HTTP response code was 500)', e.message
|
||||
assert_equal 'Invalid response object from API: "{\"bar\":\"foo\"}" (HTTP response code was 500)', e.message
|
||||
end
|
||||
|
||||
should "raise InvalidRequestError on 400" do
|
||||
@ -332,6 +332,42 @@ module Stripe
|
||||
assert_equal(true, e.json_body.kind_of?(Hash))
|
||||
end
|
||||
end
|
||||
|
||||
should "raise OAuth::InvalidRequestError when error is a string with value 'invalid_request'" do
|
||||
stub_request(:post, "#{Stripe.connect_base}/oauth/token").
|
||||
to_return(body: JSON.generate({
|
||||
error: "invalid_request",
|
||||
error_description: "No grant type specified",
|
||||
}), status: 400)
|
||||
|
||||
client = StripeClient.new
|
||||
opts = {api_base: Stripe.connect_base}
|
||||
e = assert_raises Stripe::OAuth::InvalidRequestError do
|
||||
client.execute_request(:post, '/oauth/token', opts)
|
||||
end
|
||||
|
||||
assert_equal(400, e.http_status)
|
||||
assert_equal(true, !!e.http_body)
|
||||
assert_equal('No grant type specified', e.message)
|
||||
end
|
||||
|
||||
should "raise OAuth::InvalidGrantError when error is a string with value 'invalid_grant'" do
|
||||
stub_request(:post, "#{Stripe.connect_base}/oauth/token").
|
||||
to_return(body: JSON.generate({
|
||||
error: "invalid_grant",
|
||||
error_description: "This authorization code has already been used. All tokens issued with this code have been revoked.",
|
||||
}), status: 400)
|
||||
|
||||
client = StripeClient.new
|
||||
opts = {api_base: Stripe.connect_base}
|
||||
e = assert_raises Stripe::OAuth::InvalidGrantError do
|
||||
client.execute_request(:post, '/oauth/token', opts)
|
||||
end
|
||||
|
||||
assert_equal(400, e.http_status)
|
||||
assert_equal('invalid_grant', e.code)
|
||||
assert_equal('This authorization code has already been used. All tokens issued with this code have been revoked.', e.message)
|
||||
end
|
||||
end
|
||||
|
||||
context "idempotency keys" do
|
||||
|
Loading…
x
Reference in New Issue
Block a user