oauth2 1.4.9 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +124 -24
- data/CONTRIBUTING.md +44 -0
- data/README.md +225 -97
- data/SECURITY.md +20 -0
- data/lib/oauth2/access_token.rb +41 -21
- data/lib/oauth2/authenticator.rb +9 -4
- data/lib/oauth2/client.rb +122 -80
- data/lib/oauth2/error.rb +41 -24
- data/lib/oauth2/response.rb +77 -22
- data/lib/oauth2/snaky_hash.rb +8 -0
- data/lib/oauth2/strategy/assertion.rb +63 -38
- data/lib/oauth2/strategy/auth_code.rb +13 -2
- data/lib/oauth2/strategy/client_credentials.rb +1 -1
- data/lib/oauth2/strategy/implicit.rb +7 -0
- data/lib/oauth2/version.rb +1 -59
- data/lib/oauth2.rb +19 -1
- metadata +107 -77
- data/lib/oauth2/mac_token.rb +0 -130
- data/spec/fixtures/README.md +0 -11
- data/spec/fixtures/RS256/jwtRS256.key +0 -51
- data/spec/fixtures/RS256/jwtRS256.key.pub +0 -14
- data/spec/helper.rb +0 -33
- data/spec/oauth2/access_token_spec.rb +0 -218
- data/spec/oauth2/authenticator_spec.rb +0 -86
- data/spec/oauth2/client_spec.rb +0 -556
- data/spec/oauth2/mac_token_spec.rb +0 -122
- data/spec/oauth2/response_spec.rb +0 -96
- data/spec/oauth2/strategy/assertion_spec.rb +0 -113
- data/spec/oauth2/strategy/auth_code_spec.rb +0 -108
- data/spec/oauth2/strategy/base_spec.rb +0 -7
- data/spec/oauth2/strategy/client_credentials_spec.rb +0 -71
- data/spec/oauth2/strategy/implicit_spec.rb +0 -28
- data/spec/oauth2/strategy/password_spec.rb +0 -58
- data/spec/oauth2/version_spec.rb +0 -23
data/lib/oauth2/access_token.rb
CHANGED
@@ -1,33 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module OAuth2
|
4
|
-
class AccessToken
|
5
|
-
attr_reader :client, :token, :expires_in, :expires_at, :params
|
6
|
-
attr_accessor :options, :refresh_token
|
4
|
+
class AccessToken # rubocop:disable Metrics/ClassLength
|
5
|
+
attr_reader :client, :token, :expires_in, :expires_at, :expires_latency, :params
|
6
|
+
attr_accessor :options, :refresh_token, :response
|
7
7
|
|
8
|
-
# Should these methods be deprecated?
|
9
8
|
class << self
|
10
9
|
# Initializes an AccessToken from a Hash
|
11
10
|
#
|
12
|
-
# @param [Client] the OAuth2::Client instance
|
13
|
-
# @param [Hash] a hash of AccessToken property values
|
14
|
-
# @
|
11
|
+
# @param [Client] client the OAuth2::Client instance
|
12
|
+
# @param [Hash] hash a hash of AccessToken property values
|
13
|
+
# @option hash [String] 'access_token', 'id_token', 'token', :access_token, :id_token, or :token the access token
|
14
|
+
# @return [AccessToken] the initialized AccessToken
|
15
15
|
def from_hash(client, hash)
|
16
16
|
hash = hash.dup
|
17
|
-
|
17
|
+
token = hash.delete('access_token') || hash.delete(:access_token) ||
|
18
|
+
hash.delete('id_token') || hash.delete(:id_token) ||
|
19
|
+
hash.delete('token') || hash.delete(:token) ||
|
20
|
+
hash.delete('accessToken') || hash.delete(:accessToken) ||
|
21
|
+
hash.delete('idToken') || hash.delete(:idToken)
|
22
|
+
new(client, token, hash)
|
18
23
|
end
|
19
24
|
|
20
25
|
# Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
|
21
26
|
#
|
22
27
|
# @param [Client] client the OAuth2::Client instance
|
23
28
|
# @param [String] kvform the application/x-www-form-urlencoded string
|
24
|
-
# @return [AccessToken] the
|
29
|
+
# @return [AccessToken] the initialized AccessToken
|
25
30
|
def from_kvform(client, kvform)
|
26
31
|
from_hash(client, Rack::Utils.parse_query(kvform))
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
30
|
-
#
|
35
|
+
# Initialize an AccessToken
|
31
36
|
#
|
32
37
|
# @param [Client] client the OAuth2::Client instance
|
33
38
|
# @param [String] token the Access Token value
|
@@ -35,6 +40,7 @@ module OAuth2
|
|
35
40
|
# @option opts [String] :refresh_token (nil) the refresh_token value
|
36
41
|
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
37
42
|
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
43
|
+
# @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
|
38
44
|
# @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
|
39
45
|
# one of :header, :body or :query
|
40
46
|
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
|
@@ -43,17 +49,24 @@ module OAuth2
|
|
43
49
|
def initialize(client, token, opts = {})
|
44
50
|
@client = client
|
45
51
|
@token = token.to_s
|
52
|
+
|
53
|
+
if @client.options[:raise_errors] && (@token.nil? || @token.empty?)
|
54
|
+
error = Error.new(opts)
|
55
|
+
raise(error)
|
56
|
+
end
|
46
57
|
opts = opts.dup
|
47
|
-
[
|
58
|
+
%i[refresh_token expires_in expires_at expires_latency].each do |arg|
|
48
59
|
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
|
49
60
|
end
|
50
61
|
@expires_in ||= opts.delete('expires')
|
51
62
|
@expires_in &&= @expires_in.to_i
|
52
63
|
@expires_at &&= convert_expires_at(@expires_at)
|
64
|
+
@expires_latency &&= @expires_latency.to_i
|
53
65
|
@expires_at ||= Time.now.to_i + @expires_in if @expires_in
|
54
|
-
@
|
55
|
-
|
56
|
-
:
|
66
|
+
@expires_at -= @expires_latency if @expires_latency
|
67
|
+
@options = {mode: opts.delete(:mode) || :header,
|
68
|
+
header_format: opts.delete(:header_format) || 'Bearer %s',
|
69
|
+
param_name: opts.delete(:param_name) || 'access_token'}
|
57
70
|
@params = opts
|
58
71
|
end
|
59
72
|
|
@@ -75,29 +88,36 @@ module OAuth2
|
|
75
88
|
#
|
76
89
|
# @return [Boolean]
|
77
90
|
def expired?
|
78
|
-
expires? && (expires_at
|
91
|
+
expires? && (expires_at <= Time.now.to_i)
|
79
92
|
end
|
80
93
|
|
81
94
|
# Refreshes the current Access Token
|
82
95
|
#
|
83
96
|
# @return [AccessToken] a new AccessToken
|
84
97
|
# @note options should be carried over to the new AccessToken
|
85
|
-
def refresh
|
98
|
+
def refresh(params = {}, access_token_opts = {})
|
86
99
|
raise('A refresh_token is not available') unless refresh_token
|
87
100
|
|
88
101
|
params[:grant_type] = 'refresh_token'
|
89
102
|
params[:refresh_token] = refresh_token
|
90
|
-
new_token = @client.get_token(params)
|
103
|
+
new_token = @client.get_token(params, access_token_opts)
|
91
104
|
new_token.options = options
|
92
|
-
|
105
|
+
if new_token.refresh_token
|
106
|
+
# Keep it, if there is one
|
107
|
+
else
|
108
|
+
new_token.refresh_token = refresh_token
|
109
|
+
end
|
93
110
|
new_token
|
94
111
|
end
|
112
|
+
# A compatibility alias
|
113
|
+
# @note does not modify the receiver, so bang is not the default method
|
114
|
+
alias refresh! refresh
|
95
115
|
|
96
116
|
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
|
97
117
|
#
|
98
118
|
# @return [Hash] a hash of AccessToken property values
|
99
119
|
def to_hash
|
100
|
-
params.merge(:
|
120
|
+
params.merge(access_token: token, refresh_token: refresh_token, expires_at: expires_at)
|
101
121
|
end
|
102
122
|
|
103
123
|
# Make a request with the Access Token
|
@@ -105,7 +125,7 @@ module OAuth2
|
|
105
125
|
# @param [Symbol] verb the HTTP request method
|
106
126
|
# @param [String] path the HTTP URL path of the request
|
107
127
|
# @param [Hash] opts the options to make the request with
|
108
|
-
#
|
128
|
+
# @see Client#request
|
109
129
|
def request(verb, path, opts = {}, &block)
|
110
130
|
configure_authentication!(opts)
|
111
131
|
@client.request(verb, path, opts, &block)
|
@@ -166,7 +186,7 @@ module OAuth2
|
|
166
186
|
if opts[:body].is_a?(Hash)
|
167
187
|
opts[:body][options[:param_name]] = token
|
168
188
|
else
|
169
|
-
opts[:body]
|
189
|
+
opts[:body] += "&#{options[:param_name]}=#{token}"
|
170
190
|
end
|
171
191
|
# @todo support for multi-part (file uploads)
|
172
192
|
else
|
data/lib/oauth2/authenticator.rb
CHANGED
@@ -37,7 +37,7 @@ module OAuth2
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.encode_basic_auth(user, password)
|
40
|
-
|
40
|
+
"Basic #{Base64.strict_encode64("#{user}:#{password}")}"
|
41
41
|
end
|
42
42
|
|
43
43
|
private
|
@@ -45,13 +45,18 @@ module OAuth2
|
|
45
45
|
# Adds client_id and client_secret request parameters if they are not
|
46
46
|
# already set.
|
47
47
|
def apply_params_auth(params)
|
48
|
-
|
48
|
+
result = {}
|
49
|
+
result['client_id'] = id unless id.nil?
|
50
|
+
result['client_secret'] = secret unless secret.nil?
|
51
|
+
result.merge(params)
|
49
52
|
end
|
50
53
|
|
51
54
|
# When using schemes that don't require the client_secret to be passed i.e TLS Client Auth,
|
52
55
|
# we don't want to send the secret
|
53
56
|
def apply_client_id(params)
|
54
|
-
|
57
|
+
result = {}
|
58
|
+
result['client_id'] = id unless id.nil?
|
59
|
+
result.merge(params)
|
55
60
|
end
|
56
61
|
|
57
62
|
# Adds an `Authorization` header with Basic Auth credentials if and only if
|
@@ -59,7 +64,7 @@ module OAuth2
|
|
59
64
|
def apply_basic_auth(params)
|
60
65
|
headers = params.fetch(:headers, {})
|
61
66
|
headers = basic_auth_header.merge(headers)
|
62
|
-
params.merge(:
|
67
|
+
params.merge(headers: headers)
|
63
68
|
end
|
64
69
|
|
65
70
|
# @see https://datatracker.ietf.org/doc/html/rfc2617#section-2
|
data/lib/oauth2/client.rb
CHANGED
@@ -5,9 +5,11 @@ require 'logger'
|
|
5
5
|
|
6
6
|
module OAuth2
|
7
7
|
ConnectionError = Class.new(Faraday::ConnectionFailed)
|
8
|
+
TimeoutError = Class.new(Faraday::TimeoutError)
|
9
|
+
|
8
10
|
# The OAuth2::Client class
|
9
11
|
class Client # rubocop:disable Metrics/ClassLength
|
10
|
-
RESERVED_PARAM_KEYS = %w[headers parse].freeze
|
12
|
+
RESERVED_PARAM_KEYS = %w[body headers params parse snaky].freeze
|
11
13
|
|
12
14
|
attr_reader :id, :secret, :site
|
13
15
|
attr_accessor :options
|
@@ -22,15 +24,16 @@ module OAuth2
|
|
22
24
|
# @param [Hash] options the options to create the client with
|
23
25
|
# @option options [String] :site the OAuth2 provider site host
|
24
26
|
# @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
|
25
|
-
# @option options [String] :authorize_url ('oauth/authorize') absolute or relative URL path to the Authorization endpoint
|
26
|
-
# @option options [String] :token_url ('oauth/token') absolute or relative URL path to the Token endpoint
|
27
|
-
# @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get
|
27
|
+
# @option options [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
|
28
|
+
# @option options [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
|
29
|
+
# @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get, :post, :post_with_query_string)
|
28
30
|
# @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
|
29
31
|
# @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
|
30
32
|
# @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
|
31
33
|
# @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
|
32
34
|
# @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
|
33
|
-
# @option options [Proc]
|
35
|
+
# @option options [Proc] :extract_access_token proc that takes the client and the response Hash and extracts the access token from the response (DEPRECATED)
|
36
|
+
# @option options [Class] :access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken, @version 2.0+
|
34
37
|
# @yield [builder] The Faraday connection builder
|
35
38
|
def initialize(client_id, client_secret, options = {}, &block)
|
36
39
|
opts = options.dup
|
@@ -38,16 +41,19 @@ module OAuth2
|
|
38
41
|
@secret = client_secret
|
39
42
|
@site = opts.delete(:site)
|
40
43
|
ssl = opts.delete(:ssl)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
44
|
+
warn('OAuth2::Client#initialize argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class`.') if opts[:extract_access_token]
|
45
|
+
@options = {
|
46
|
+
authorize_url: 'oauth/authorize',
|
47
|
+
token_url: 'oauth/token',
|
48
|
+
token_method: :post,
|
49
|
+
auth_scheme: :basic_auth,
|
50
|
+
connection_opts: {},
|
51
|
+
connection_build: block,
|
52
|
+
max_redirects: 5,
|
53
|
+
raise_errors: true,
|
54
|
+
logger: ::Logger.new($stdout),
|
55
|
+
access_token_class: AccessToken,
|
56
|
+
}.merge(opts)
|
51
57
|
@options[:connection_opts][:ssl] = ssl if ssl
|
52
58
|
end
|
53
59
|
|
@@ -89,6 +95,9 @@ module OAuth2
|
|
89
95
|
end
|
90
96
|
|
91
97
|
# Makes a request relative to the specified site root.
|
98
|
+
# Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616),
|
99
|
+
# allowing the use of relative URLs in Location headers.
|
100
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2
|
92
101
|
#
|
93
102
|
# @param [Symbol] verb one of :get, :post, :put, :delete
|
94
103
|
# @param [String] url URL path of request
|
@@ -99,20 +108,10 @@ module OAuth2
|
|
99
108
|
# @option opts [Boolean] :raise_errors whether or not to raise an OAuth2::Error on 400+ status
|
100
109
|
# code response for this request. Will default to client option
|
101
110
|
# @option opts [Symbol] :parse @see Response::initialize
|
102
|
-
# @
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
begin
|
107
|
-
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
108
|
-
req.params.update(opts[:params]) if opts[:params]
|
109
|
-
yield(req) if block_given?
|
110
|
-
end
|
111
|
-
rescue Faraday::ConnectionFailed => e
|
112
|
-
raise ConnectionError, e
|
113
|
-
end
|
114
|
-
|
115
|
-
response = Response.new(response, :parse => opts[:parse])
|
111
|
+
# @option opts [true, false] :snaky (true) @see Response::initialize
|
112
|
+
# @yield [req] @see Faraday::Connection#run_request
|
113
|
+
def request(verb, url, opts = {}, &block)
|
114
|
+
response = execute_request(verb, url, opts, &block)
|
116
115
|
|
117
116
|
case response.status
|
118
117
|
when 301, 302, 303, 307
|
@@ -126,7 +125,8 @@ module OAuth2
|
|
126
125
|
end
|
127
126
|
location = response.headers['location']
|
128
127
|
if location
|
129
|
-
|
128
|
+
full_location = response.response.env.url.merge(location)
|
129
|
+
request(verb, full_location, opts)
|
130
130
|
else
|
131
131
|
error = Error.new(response)
|
132
132
|
raise(error, "Got #{response.status} status code, but no Location header was present")
|
@@ -138,7 +138,6 @@ module OAuth2
|
|
138
138
|
error = Error.new(response)
|
139
139
|
raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
|
140
140
|
|
141
|
-
response.error = error
|
142
141
|
response
|
143
142
|
else
|
144
143
|
error = Error.new(response)
|
@@ -148,53 +147,54 @@ module OAuth2
|
|
148
147
|
|
149
148
|
# Initializes an AccessToken by making a request to the token endpoint
|
150
149
|
#
|
151
|
-
# @param params [Hash] a Hash of params for the token endpoint
|
150
|
+
# @param params [Hash] a Hash of params for the token endpoint, except:
|
151
|
+
# @option params [Symbol] :parse @see Response#initialize
|
152
|
+
# @option params [true, false] :snaky (true) @see Response#initialize
|
152
153
|
# @param access_token_opts [Hash] access token options, to pass to the AccessToken object
|
153
|
-
# @param
|
154
|
+
# @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED)
|
155
|
+
# @yield [req] @see Faraday::Connection#run_request
|
154
156
|
# @return [AccessToken] the initialized AccessToken
|
155
|
-
def get_token(params, access_token_opts = {}, extract_access_token =
|
157
|
+
def get_token(params, access_token_opts = {}, extract_access_token = nil, &block)
|
158
|
+
warn('OAuth2::Client#get_token argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class` on #initialize.') if extract_access_token
|
159
|
+
extract_access_token ||= options[:extract_access_token]
|
156
160
|
params = params.map do |key, value|
|
157
161
|
if RESERVED_PARAM_KEYS.include?(key)
|
158
162
|
[key.to_sym, value]
|
159
163
|
else
|
160
164
|
[key, value]
|
161
165
|
end
|
162
|
-
end
|
163
|
-
|
166
|
+
end.to_h
|
167
|
+
|
168
|
+
parse = params.key?(:parse) ? params.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
|
169
|
+
snaky = params.key?(:snaky) ? params.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky]
|
164
170
|
|
171
|
+
request_opts = {
|
172
|
+
raise_errors: options[:raise_errors],
|
173
|
+
parse: parse,
|
174
|
+
snaky: snaky,
|
175
|
+
}
|
165
176
|
params = authenticator.apply(params)
|
166
|
-
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
|
167
177
|
headers = params.delete(:headers) || {}
|
168
178
|
if options[:token_method] == :post
|
169
|
-
|
170
|
-
|
179
|
+
request_opts[:body] = params
|
180
|
+
request_opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
|
171
181
|
else
|
172
|
-
|
173
|
-
|
182
|
+
request_opts[:params] = params
|
183
|
+
request_opts[:headers] = {}
|
174
184
|
end
|
175
|
-
|
185
|
+
request_opts[:headers].merge!(headers)
|
176
186
|
http_method = options[:token_method]
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
(response.parsed['access_token'] || response.parsed['id_token'])
|
188
|
-
)
|
189
|
-
|
190
|
-
if options[:raise_errors] && !response_contains_token
|
191
|
-
error = Error.new(response)
|
192
|
-
raise(error)
|
193
|
-
elsif !response_contains_token
|
194
|
-
return nil
|
187
|
+
http_method = :post if http_method == :post_with_query_string
|
188
|
+
response = request(http_method, token_url, request_opts, &block)
|
189
|
+
|
190
|
+
# In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
|
191
|
+
# We preserve this behavior here, but a custom access_token_class that implements #from_hash
|
192
|
+
# should be used instead.
|
193
|
+
if extract_access_token
|
194
|
+
parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
|
195
|
+
else
|
196
|
+
parse_response(response, access_token_opts)
|
195
197
|
end
|
196
|
-
|
197
|
-
access_token
|
198
198
|
end
|
199
199
|
|
200
200
|
# The Authorization Code strategy
|
@@ -253,13 +253,28 @@ module OAuth2
|
|
253
253
|
end
|
254
254
|
end
|
255
255
|
|
256
|
-
DEFAULT_EXTRACT_ACCESS_TOKEN = proc do |client, hash|
|
257
|
-
token = hash.delete('access_token') || hash.delete(:access_token)
|
258
|
-
token && AccessToken.new(client, token, hash)
|
259
|
-
end
|
260
|
-
|
261
256
|
private
|
262
257
|
|
258
|
+
def execute_request(verb, url, opts = {})
|
259
|
+
url = connection.build_url(url).to_s
|
260
|
+
|
261
|
+
begin
|
262
|
+
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
|
263
|
+
req.params.update(opts[:params]) if opts[:params]
|
264
|
+
yield(req) if block_given?
|
265
|
+
end
|
266
|
+
rescue Faraday::ConnectionFailed => e
|
267
|
+
raise ConnectionError, e
|
268
|
+
rescue Faraday::TimeoutError => e
|
269
|
+
raise TimeoutError, e
|
270
|
+
end
|
271
|
+
|
272
|
+
parse = opts.key?(:parse) ? opts.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
|
273
|
+
snaky = opts.key?(:snaky) ? opts.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky]
|
274
|
+
|
275
|
+
Response.new(response, parse: parse, snaky: snaky)
|
276
|
+
end
|
277
|
+
|
263
278
|
# Returns the authenticator object
|
264
279
|
#
|
265
280
|
# @return [Authenticator] the initialized Authenticator
|
@@ -267,26 +282,53 @@ module OAuth2
|
|
267
282
|
Authenticator.new(id, secret, options[:auth_scheme])
|
268
283
|
end
|
269
284
|
|
285
|
+
def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
|
286
|
+
access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
|
287
|
+
|
288
|
+
return access_token if access_token
|
289
|
+
|
290
|
+
if options[:raise_errors]
|
291
|
+
error = Error.new(response)
|
292
|
+
raise(error)
|
293
|
+
end
|
294
|
+
|
295
|
+
nil
|
296
|
+
end
|
297
|
+
|
298
|
+
def parse_response(response, access_token_opts)
|
299
|
+
access_token_class = options[:access_token_class]
|
300
|
+
data = response.parsed
|
301
|
+
|
302
|
+
unless data.is_a?(Hash) && !data.empty?
|
303
|
+
return unless options[:raise_errors]
|
304
|
+
|
305
|
+
error = Error.new(response)
|
306
|
+
raise(error)
|
307
|
+
end
|
308
|
+
|
309
|
+
build_access_token(response, access_token_opts, access_token_class)
|
310
|
+
end
|
311
|
+
|
270
312
|
# Builds the access token from the response of the HTTP call
|
271
313
|
#
|
272
314
|
# @return [AccessToken] the initialized AccessToken
|
273
|
-
def build_access_token(response, access_token_opts,
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
hash = parsed_response.merge(access_token_opts)
|
278
|
-
|
279
|
-
# Provide backwards compatibility for old AccessToken.form_hash pattern
|
280
|
-
# Will be deprecated in 2.x
|
281
|
-
if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
|
282
|
-
extract_access_token.from_hash(self, hash)
|
283
|
-
else
|
284
|
-
extract_access_token.call(self, hash)
|
315
|
+
def build_access_token(response, access_token_opts, access_token_class)
|
316
|
+
access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token|
|
317
|
+
access_token.response = response if access_token.respond_to?(:response=)
|
285
318
|
end
|
286
319
|
end
|
287
320
|
|
321
|
+
# Builds the access token from the response of the HTTP call with legacy extract_access_token
|
322
|
+
#
|
323
|
+
# @return [AccessToken] the initialized AccessToken
|
324
|
+
def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
|
325
|
+
extract_access_token.call(self, response.parsed.merge(access_token_opts))
|
326
|
+
rescue StandardError
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
|
288
330
|
def oauth_debug_logging(builder)
|
289
|
-
builder.response :logger, options[:logger], :
|
331
|
+
builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true'
|
290
332
|
end
|
291
333
|
end
|
292
334
|
end
|
data/lib/oauth2/error.rb
CHANGED
@@ -2,41 +2,58 @@
|
|
2
2
|
|
3
3
|
module OAuth2
|
4
4
|
class Error < StandardError
|
5
|
-
attr_reader :response, :code, :description
|
5
|
+
attr_reader :response, :body, :code, :description
|
6
6
|
|
7
|
-
# standard error
|
8
|
-
#
|
7
|
+
# standard error codes include:
|
8
|
+
# 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
|
9
|
+
# response might be a Response object, or the response.parsed hash
|
9
10
|
def initialize(response)
|
10
|
-
response.error = self
|
11
11
|
@response = response
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
if response.respond_to?(:parsed)
|
13
|
+
if response.parsed.is_a?(Hash)
|
14
|
+
@code = response.parsed['error']
|
15
|
+
@description = response.parsed['error_description']
|
16
|
+
end
|
17
|
+
elsif response.is_a?(Hash)
|
18
|
+
@code = response['error']
|
19
|
+
@description = response['error_description']
|
17
20
|
end
|
18
|
-
|
19
|
-
|
21
|
+
@body = if response.respond_to?(:body)
|
22
|
+
response.body
|
23
|
+
else
|
24
|
+
@response
|
25
|
+
end
|
26
|
+
message_opts = parse_error_description(@code, @description)
|
27
|
+
super(error_message(@body, message_opts))
|
20
28
|
end
|
21
29
|
|
22
|
-
|
23
|
-
|
24
|
-
# @param [String] opts :error_description error description to show first line
|
30
|
+
private
|
31
|
+
|
25
32
|
def error_message(response_body, opts = {})
|
26
|
-
|
33
|
+
lines = []
|
34
|
+
|
35
|
+
lines << opts[:error_description] if opts[:error_description]
|
27
36
|
|
28
|
-
|
37
|
+
error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
|
38
|
+
script_encoding = opts[:error_description].encoding
|
39
|
+
response_body.encode(script_encoding, invalid: :replace, undef: :replace)
|
40
|
+
else
|
41
|
+
response_body
|
42
|
+
end
|
43
|
+
|
44
|
+
lines << error_string
|
45
|
+
|
46
|
+
lines.join("\n")
|
47
|
+
end
|
29
48
|
|
30
|
-
|
31
|
-
|
32
|
-
response_body.encode(script_encoding, :invalid => :replace, :undef => :replace)
|
33
|
-
else
|
34
|
-
response_body
|
35
|
-
end
|
49
|
+
def parse_error_description(code, description)
|
50
|
+
return {} unless code || description
|
36
51
|
|
37
|
-
|
52
|
+
error_description = ''
|
53
|
+
error_description += "#{code}: " if code
|
54
|
+
error_description += description if description
|
38
55
|
|
39
|
-
|
56
|
+
{error_description: error_description}
|
40
57
|
end
|
41
58
|
end
|
42
59
|
end
|