oauth2 1.4.7 → 2.0.2

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.
@@ -1,31 +1,36 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OAuth2
2
- class AccessToken
3
- attr_reader :client, :token, :expires_in, :expires_at, :params
4
- 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
5
7
 
6
- # Should these methods be deprecated?
7
8
  class << self
8
9
  # Initializes an AccessToken from a Hash
9
10
  #
10
- # @param [Client] the OAuth2::Client instance
11
- # @param [Hash] a hash of AccessToken property values
12
- # @return [AccessToken] the initalized AccessToken
11
+ # @param client [Client] the OAuth2::Client instance
12
+ # @param hash [Hash] a hash of AccessToken property values
13
+ # @return [AccessToken] the initialized AccessToken
13
14
  def from_hash(client, hash)
14
15
  hash = hash.dup
15
- new(client, hash.delete('access_token') || hash.delete(:access_token), hash)
16
+ new(client, hash.delete('access_token') || hash.delete(:access_token) || hash.delete('token') || hash.delete(:token), hash)
16
17
  end
17
18
 
18
19
  # Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
19
20
  #
20
21
  # @param [Client] client the OAuth2::Client instance
21
22
  # @param [String] kvform the application/x-www-form-urlencoded string
22
- # @return [AccessToken] the initalized AccessToken
23
+ # @return [AccessToken] the initialized AccessToken
23
24
  def from_kvform(client, kvform)
24
25
  from_hash(client, Rack::Utils.parse_query(kvform))
25
26
  end
27
+
28
+ def contains_token?(hash)
29
+ hash.key?('access_token') || hash.key?('id_token') || hash.key?('token')
30
+ end
26
31
  end
27
32
 
28
- # Initalize an AccessToken
33
+ # Initialize an AccessToken
29
34
  #
30
35
  # @param [Client] client the OAuth2::Client instance
31
36
  # @param [String] token the Access Token value
@@ -33,25 +38,28 @@ module OAuth2
33
38
  # @option opts [String] :refresh_token (nil) the refresh_token value
34
39
  # @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
35
40
  # @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
41
+ # @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
36
42
  # @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
37
43
  # one of :header, :body or :query
38
44
  # @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
39
45
  # @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
40
46
  # Access Token value in :body or :query transmission mode
41
- def initialize(client, token, opts = {}) # rubocop:disable Metrics/AbcSize
47
+ def initialize(client, token, opts = {})
42
48
  @client = client
43
49
  @token = token.to_s
44
50
  opts = opts.dup
45
- [:refresh_token, :expires_in, :expires_at].each do |arg|
51
+ %i[refresh_token expires_in expires_at expires_latency].each do |arg|
46
52
  instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
47
53
  end
48
54
  @expires_in ||= opts.delete('expires')
49
55
  @expires_in &&= @expires_in.to_i
50
56
  @expires_at &&= convert_expires_at(@expires_at)
57
+ @expires_latency &&= @expires_latency.to_i
51
58
  @expires_at ||= Time.now.to_i + @expires_in if @expires_in
52
- @options = {:mode => opts.delete(:mode) || :header,
53
- :header_format => opts.delete(:header_format) || 'Bearer %s',
54
- :param_name => opts.delete(:param_name) || 'access_token'}
59
+ @expires_at -= @expires_latency if @expires_latency
60
+ @options = {mode: opts.delete(:mode) || :header,
61
+ header_format: opts.delete(:header_format) || 'Bearer %s',
62
+ param_name: opts.delete(:param_name) || 'access_token'}
55
63
  @params = opts
56
64
  end
57
65
 
@@ -73,29 +81,32 @@ module OAuth2
73
81
  #
74
82
  # @return [Boolean]
75
83
  def expired?
76
- expires? && (expires_at < Time.now.to_i)
84
+ expires? && (expires_at <= Time.now.to_i)
77
85
  end
78
86
 
79
87
  # Refreshes the current Access Token
80
88
  #
81
89
  # @return [AccessToken] a new AccessToken
82
90
  # @note options should be carried over to the new AccessToken
83
- def refresh!(params = {})
91
+ def refresh(params = {}, access_token_opts = {})
84
92
  raise('A refresh_token is not available') unless refresh_token
85
93
 
86
94
  params[:grant_type] = 'refresh_token'
87
95
  params[:refresh_token] = refresh_token
88
- new_token = @client.get_token(params)
96
+ new_token = @client.get_token(params, access_token_opts)
89
97
  new_token.options = options
90
98
  new_token.refresh_token = refresh_token unless new_token.refresh_token
91
99
  new_token
92
100
  end
101
+ # A compatibility alias
102
+ # @note does not modify the receiver, so bang is not the default method
103
+ alias refresh! refresh
93
104
 
94
105
  # Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
95
106
  #
96
107
  # @return [Hash] a hash of AccessToken property values
97
108
  def to_hash
98
- params.merge(:access_token => token, :refresh_token => refresh_token, :expires_at => expires_at)
109
+ params.merge(access_token: token, refresh_token: refresh_token, expires_at: expires_at)
99
110
  end
100
111
 
101
112
  # Make a request with the Access Token
@@ -151,7 +162,7 @@ module OAuth2
151
162
 
152
163
  private
153
164
 
154
- def configure_authentication!(opts) # rubocop:disable Metrics/AbcSize
165
+ def configure_authentication!(opts)
155
166
  case options[:mode]
156
167
  when :header
157
168
  opts[:headers] ||= {}
@@ -164,7 +175,7 @@ module OAuth2
164
175
  if opts[:body].is_a?(Hash)
165
176
  opts[:body][options[:param_name]] = token
166
177
  else
167
- opts[:body] << "&#{options[:param_name]}=#{token}"
178
+ opts[:body] += "&#{options[:param_name]}=#{token}"
168
179
  end
169
180
  # @todo support for multi-part (file uploads)
170
181
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'base64'
2
4
 
3
5
  module OAuth2
@@ -35,7 +37,7 @@ module OAuth2
35
37
  end
36
38
 
37
39
  def self.encode_basic_auth(user, password)
38
- 'Basic ' + Base64.encode64(user + ':' + password).delete("\n")
40
+ "Basic #{Base64.strict_encode64("#{user}:#{password}")}"
39
41
  end
40
42
 
41
43
  private
@@ -43,13 +45,18 @@ module OAuth2
43
45
  # Adds client_id and client_secret request parameters if they are not
44
46
  # already set.
45
47
  def apply_params_auth(params)
46
- {'client_id' => id, 'client_secret' => secret}.merge(params)
48
+ result = {}
49
+ result['client_id'] = id unless id.nil?
50
+ result['client_secret'] = secret unless secret.nil?
51
+ result.merge(params)
47
52
  end
48
53
 
49
54
  # When using schemes that don't require the client_secret to be passed i.e TLS Client Auth,
50
55
  # we don't want to send the secret
51
56
  def apply_client_id(params)
52
- {'client_id' => id}.merge(params)
57
+ result = {}
58
+ result['client_id'] = id unless id.nil?
59
+ result.merge(params)
53
60
  end
54
61
 
55
62
  # Adds an `Authorization` header with Basic Auth credentials if and only if
@@ -57,10 +64,10 @@ module OAuth2
57
64
  def apply_basic_auth(params)
58
65
  headers = params.fetch(:headers, {})
59
66
  headers = basic_auth_header.merge(headers)
60
- params.merge(:headers => headers)
67
+ params.merge(headers: headers)
61
68
  end
62
69
 
63
- # @see https://tools.ietf.org/html/rfc2617#section-2
70
+ # @see https://datatracker.ietf.org/doc/html/rfc2617#section-2
64
71
  def basic_auth_header
65
72
  {'Authorization' => self.class.encode_basic_auth(id, secret)}
66
73
  end
data/lib/oauth2/client.rb CHANGED
@@ -1,7 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'logger'
3
5
 
4
6
  module OAuth2
7
+ ConnectionError = Class.new(Faraday::ConnectionFailed)
8
+ TimeoutError = Class.new(Faraday::TimeoutError)
9
+
5
10
  # The OAuth2::Client class
6
11
  class Client # rubocop:disable Metrics/ClassLength
7
12
  RESERVED_PARAM_KEYS = %w[headers parse].freeze
@@ -16,17 +21,19 @@ module OAuth2
16
21
  #
17
22
  # @param [String] client_id the client_id value
18
23
  # @param [String] client_secret the client_secret value
19
- # @param [Hash] opts the options to create the client with
20
- # @option opts [String] :site the OAuth2 provider site host
21
- # @option opts [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
22
- # @option opts [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
23
- # @option opts [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
24
- # @option opts [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post)
25
- # @option opts [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
26
- # @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
27
- # @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow
28
- # @option opts [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
29
- # @option opts [Proc] :extract_access_token proc that extracts the access token from the response
24
+ # @param [Hash] options the options to create the client with
25
+ # @option options [String] :site the OAuth2 provider site host
26
+ # @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
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)
30
+ # @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
31
+ # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
32
+ # @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
33
+ # @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
34
+ # @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
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+
30
37
  # @yield [builder] The Faraday connection builder
31
38
  def initialize(client_id, client_secret, options = {}, &block)
32
39
  opts = options.dup
@@ -36,22 +43,23 @@ module OAuth2
36
43
  ssl = opts.delete(:ssl)
37
44
 
38
45
  @options = {
39
- :authorize_url => '/oauth/authorize',
40
- :token_url => '/oauth/token',
41
- :token_method => :post,
42
- :auth_scheme => :request_body,
43
- :connection_opts => {},
44
- :connection_build => block,
45
- :max_redirects => 5,
46
- :raise_errors => true,
47
- :extract_access_token => DEFAULT_EXTRACT_ACCESS_TOKEN,
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,
48
56
  }.merge(opts)
49
57
  @options[:connection_opts][:ssl] = ssl if ssl
50
58
  end
51
59
 
52
60
  # Set the site host
53
61
  #
54
- # @param [String] the OAuth2 provider site host
62
+ # @param value [String] the OAuth2 provider site host
55
63
  def site=(value)
56
64
  @connection = nil
57
65
  @site = value
@@ -59,15 +67,16 @@ module OAuth2
59
67
 
60
68
  # The Faraday connection object
61
69
  def connection
62
- @connection ||= begin
63
- conn = Faraday.new(site, options[:connection_opts])
64
- if options[:connection_build]
65
- conn.build do |b|
66
- options[:connection_build].call(b)
70
+ @connection ||=
71
+ Faraday.new(site, options[:connection_opts]) do |builder|
72
+ oauth_debug_logging(builder)
73
+ if options[:connection_build]
74
+ options[:connection_build].call(builder)
75
+ else
76
+ builder.request :url_encoded # form-encode POST params
77
+ builder.adapter Faraday.default_adapter # make requests with Net::HTTP
67
78
  end
68
79
  end
69
- conn
70
- end
71
80
  end
72
81
 
73
82
  # The authorize endpoint URL of the OAuth2 provider
@@ -86,6 +95,9 @@ module OAuth2
86
95
  end
87
96
 
88
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
89
101
  #
90
102
  # @param [Symbol] verb one of :get, :post, :put, :delete
91
103
  # @param [String] url URL path of request
@@ -97,16 +109,8 @@ module OAuth2
97
109
  # code response for this request. Will default to client option
98
110
  # @option opts [Symbol] :parse @see Response::initialize
99
111
  # @yield [req] The Faraday request
100
- def request(verb, url, opts = {}) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
101
- connection.response :logger, ::Logger.new($stdout) if ENV['OAUTH_DEBUG'] == 'true'
102
-
103
- url = connection.build_url(url).to_s
104
-
105
- response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
106
- req.params.update(opts[:params]) if opts[:params]
107
- yield(req) if block_given?
108
- end
109
- response = Response.new(response, :parse => opts[:parse])
112
+ def request(verb, url, opts = {})
113
+ response = execute_request(verb, url, opts)
110
114
 
111
115
  case response.status
112
116
  when 301, 302, 303, 307
@@ -118,7 +122,14 @@ module OAuth2
118
122
  verb = :get
119
123
  opts.delete(:body)
120
124
  end
121
- request(verb, response.headers['location'], opts)
125
+ location = response.headers['location']
126
+ if location
127
+ full_location = response.response.env.url.merge(location)
128
+ request(verb, full_location, opts)
129
+ else
130
+ error = Error.new(response)
131
+ raise(error, "Got #{response.status} status code, but no Location header was present")
132
+ end
122
133
  when 200..299, 300..399
123
134
  # on non-redirecting 3xx statuses, just return the response
124
135
  response
@@ -126,7 +137,6 @@ module OAuth2
126
137
  error = Error.new(response)
127
138
  raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
128
139
 
129
- response.error = error
130
140
  response
131
141
  else
132
142
  error = Error.new(response)
@@ -136,22 +146,21 @@ module OAuth2
136
146
 
137
147
  # Initializes an AccessToken by making a request to the token endpoint
138
148
  #
139
- # @param [Hash] params a Hash of params for the token endpoint
140
- # @param [Hash] access token options, to pass to the AccessToken object
141
- # @param [Class] class of access token for easier subclassing OAuth2::AccessToken
149
+ # @param params [Hash] a Hash of params for the token endpoint
150
+ # @param access_token_opts [Hash] access token options, to pass to the AccessToken object
151
+ # @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED)
142
152
  # @return [AccessToken] the initialized AccessToken
143
- def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token]) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
153
+ def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token])
144
154
  params = params.map do |key, value|
145
155
  if RESERVED_PARAM_KEYS.include?(key)
146
156
  [key.to_sym, value]
147
157
  else
148
158
  [key, value]
149
159
  end
150
- end
151
- params = Hash[params]
160
+ end.to_h
152
161
 
153
- params = Authenticator.new(id, secret, options[:auth_scheme]).apply(params)
154
- opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
162
+ params = authenticator.apply(params)
163
+ opts = {raise_errors: options[:raise_errors], parse: params.delete(:parse)}
155
164
  headers = params.delete(:headers) || {}
156
165
  if options[:token_method] == :post
157
166
  opts[:body] = params
@@ -161,45 +170,44 @@ module OAuth2
161
170
  opts[:headers] = {}
162
171
  end
163
172
  opts[:headers].merge!(headers)
164
- response = request(options[:token_method], token_url, opts)
165
-
166
- access_token = begin
167
- build_access_token(response, access_token_opts, extract_access_token)
168
- rescue StandardError
169
- nil
170
- end
173
+ http_method = options[:token_method]
174
+ http_method = :post if http_method == :post_with_query_string
175
+ response = request(http_method, token_url, opts)
171
176
 
172
- if options[:raise_errors] && !access_token
173
- error = Error.new(response)
174
- raise(error)
177
+ # In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
178
+ # We preserve this behavior here, but a custom access_token_class that implements #from_hash
179
+ # should be used instead.
180
+ if extract_access_token
181
+ parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
182
+ else
183
+ parse_response(response, access_token_opts)
175
184
  end
176
- access_token
177
185
  end
178
186
 
179
187
  # The Authorization Code strategy
180
188
  #
181
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
189
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
182
190
  def auth_code
183
191
  @auth_code ||= OAuth2::Strategy::AuthCode.new(self)
184
192
  end
185
193
 
186
194
  # The Implicit strategy
187
195
  #
188
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
196
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
189
197
  def implicit
190
198
  @implicit ||= OAuth2::Strategy::Implicit.new(self)
191
199
  end
192
200
 
193
201
  # The Resource Owner Password Credentials strategy
194
202
  #
195
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
203
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
196
204
  def password
197
205
  @password ||= OAuth2::Strategy::Password.new(self)
198
206
  end
199
207
 
200
208
  # The Client Credentials strategy
201
209
  #
202
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
210
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
203
211
  def client_credentials
204
212
  @client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
205
213
  end
@@ -219,10 +227,10 @@ module OAuth2
219
227
  #
220
228
  # @api semipublic
221
229
  #
222
- # @see https://tools.ietf.org/html/rfc6749#section-4.1
223
- # @see https://tools.ietf.org/html/rfc6749#section-4.1.3
224
- # @see https://tools.ietf.org/html/rfc6749#section-4.2.1
225
- # @see https://tools.ietf.org/html/rfc6749#section-10.6
230
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
231
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
232
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
233
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
226
234
  # @return [Hash] the params to add to a request or URL
227
235
  def redirection_params
228
236
  if options[:redirect_uri]
@@ -232,26 +240,79 @@ module OAuth2
232
240
  end
233
241
  end
234
242
 
235
- DEFAULT_EXTRACT_ACCESS_TOKEN = proc do |client, hash|
236
- token = hash.delete('access_token') || hash.delete(:access_token)
237
- token && AccessToken.new(client, token, hash)
243
+ private
244
+
245
+ def execute_request(verb, url, opts = {})
246
+ url = connection.build_url(url).to_s
247
+
248
+ begin
249
+ response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
250
+ req.params.update(opts[:params]) if opts[:params]
251
+ yield(req) if block_given?
252
+ end
253
+ rescue Faraday::ConnectionFailed => e
254
+ raise ConnectionError, e
255
+ rescue Faraday::TimeoutError => e
256
+ raise TimeoutError, e
257
+ end
258
+
259
+ Response.new(response, parse: opts[:parse])
238
260
  end
239
261
 
240
- private
262
+ # Returns the authenticator object
263
+ #
264
+ # @return [Authenticator] the initialized Authenticator
265
+ def authenticator
266
+ Authenticator.new(id, secret, options[:auth_scheme])
267
+ end
241
268
 
242
- def build_access_token(response, access_token_opts, extract_access_token)
243
- parsed_response = response.parsed.dup
244
- return unless parsed_response.is_a?(Hash)
269
+ def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
270
+ access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
245
271
 
246
- hash = parsed_response.merge(access_token_opts)
272
+ return access_token if access_token
247
273
 
248
- # Provide backwards compatibility for old AcessToken.form_hash pattern
249
- # Should be deprecated in 2.x
250
- if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
251
- extract_access_token.from_hash(self, hash)
252
- else
253
- extract_access_token.call(self, hash)
274
+ if options[:raise_errors]
275
+ error = Error.new(response)
276
+ raise(error)
277
+ end
278
+
279
+ nil
280
+ end
281
+
282
+ def parse_response(response, access_token_opts)
283
+ access_token_class = options[:access_token_class]
284
+ data = response.parsed
285
+
286
+ unless data.is_a?(Hash) && access_token_class.contains_token?(data)
287
+ return unless options[:raise_errors]
288
+
289
+ error = Error.new(response)
290
+ raise(error)
254
291
  end
292
+
293
+ build_access_token(response, access_token_opts, access_token_class)
294
+ end
295
+
296
+ # Builds the access token from the response of the HTTP call
297
+ #
298
+ # @return [AccessToken] the initialized AccessToken
299
+ def build_access_token(response, access_token_opts, access_token_class)
300
+ access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token|
301
+ access_token.response = response if access_token.respond_to?(:response=)
302
+ end
303
+ end
304
+
305
+ # Builds the access token from the response of the HTTP call with legacy extract_access_token
306
+ #
307
+ # @return [AccessToken] the initialized AccessToken
308
+ def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
309
+ extract_access_token.call(self, response.parsed.merge(access_token_opts))
310
+ rescue StandardError
311
+ nil
312
+ end
313
+
314
+ def oauth_debug_logging(builder)
315
+ builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true'
255
316
  end
256
317
  end
257
318
  end
data/lib/oauth2/error.rb CHANGED
@@ -1,40 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OAuth2
2
4
  class Error < StandardError
3
5
  attr_reader :response, :code, :description
4
6
 
5
- # standard error values include:
6
- # :invalid_request, :invalid_client, :invalid_token, :invalid_grant, :unsupported_grant_type, :invalid_scope
7
+ # standard error codes include:
8
+ # 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
7
9
  def initialize(response)
8
- response.error = self
9
10
  @response = response
11
+ message_opts = {}
10
12
 
11
13
  if response.parsed.is_a?(Hash)
12
14
  @code = response.parsed['error']
13
15
  @description = response.parsed['error_description']
14
- error_description = "#{@code}: #{@description}"
16
+ message_opts = parse_error_description(@code, @description)
15
17
  end
16
18
 
17
- super(error_message(response.body, :error_description => error_description))
19
+ super(error_message(response.body, message_opts))
18
20
  end
19
21
 
20
- # Makes a error message
21
- # @param [String] response_body response body of request
22
- # @param [String] opts :error_description error description to show first line
22
+ private
23
+
23
24
  def error_message(response_body, opts = {})
24
- message = []
25
+ lines = []
26
+
27
+ lines << opts[:error_description] if opts[:error_description]
25
28
 
26
- opts[:error_description] && message << opts[:error_description]
29
+ error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
30
+ script_encoding = opts[:error_description].encoding
31
+ response_body.encode(script_encoding, invalid: :replace, undef: :replace)
32
+ else
33
+ response_body
34
+ end
35
+
36
+ lines << error_string
37
+
38
+ lines.join("\n")
39
+ end
27
40
 
28
- error_message = if opts[:error_description] && opts[:error_description].respond_to?(:encoding)
29
- script_encoding = opts[:error_description].encoding
30
- response_body.encode(script_encoding, :invalid => :replace, :undef => :replace)
31
- else
32
- response_body
33
- end
41
+ def parse_error_description(code, description)
42
+ return {} unless code || description
34
43
 
35
- message << error_message
44
+ error_description = ''
45
+ error_description += "#{code}: " if code
46
+ error_description += description if description
36
47
 
37
- message.join("\n")
48
+ {error_description: error_description}
38
49
  end
39
50
  end
40
51
  end