oauth2 1.4.7 → 2.0.1

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 = {}, access_token_class: self.class)
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, access_token_class: access_token_class)
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,10 @@
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)
5
8
  # The OAuth2::Client class
6
9
  class Client # rubocop:disable Metrics/ClassLength
7
10
  RESERVED_PARAM_KEYS = %w[headers parse].freeze
@@ -16,17 +19,18 @@ module OAuth2
16
19
  #
17
20
  # @param [String] client_id the client_id value
18
21
  # @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
22
+ # @param [Hash] options the options to create the client with
23
+ # @option options [String] :site the OAuth2 provider site host
24
+ # @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, :post, :post_with_query_string)
28
+ # @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
29
+ # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
30
+ # @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
31
+ # @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
32
+ # @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
33
+ # @option options [Proc] :extract_access_token proc that takes the client and the response Hash and extracts the access token from the response (DEPRECATED)
30
34
  # @yield [builder] The Faraday connection builder
31
35
  def initialize(client_id, client_secret, options = {}, &block)
32
36
  opts = options.dup
@@ -36,22 +40,22 @@ module OAuth2
36
40
  ssl = opts.delete(:ssl)
37
41
 
38
42
  @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,
43
+ authorize_url: 'oauth/authorize',
44
+ token_url: 'oauth/token',
45
+ token_method: :post,
46
+ auth_scheme: :basic_auth,
47
+ connection_opts: {},
48
+ connection_build: block,
49
+ max_redirects: 5,
50
+ raise_errors: true,
51
+ logger: ::Logger.new($stdout),
48
52
  }.merge(opts)
49
53
  @options[:connection_opts][:ssl] = ssl if ssl
50
54
  end
51
55
 
52
56
  # Set the site host
53
57
  #
54
- # @param [String] the OAuth2 provider site host
58
+ # @param value [String] the OAuth2 provider site host
55
59
  def site=(value)
56
60
  @connection = nil
57
61
  @site = value
@@ -59,15 +63,16 @@ module OAuth2
59
63
 
60
64
  # The Faraday connection object
61
65
  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)
66
+ @connection ||=
67
+ Faraday.new(site, options[:connection_opts]) do |builder|
68
+ oauth_debug_logging(builder)
69
+ if options[:connection_build]
70
+ options[:connection_build].call(builder)
71
+ else
72
+ builder.request :url_encoded # form-encode POST params
73
+ builder.adapter Faraday.default_adapter # make requests with Net::HTTP
67
74
  end
68
75
  end
69
- conn
70
- end
71
76
  end
72
77
 
73
78
  # The authorize endpoint URL of the OAuth2 provider
@@ -86,6 +91,9 @@ module OAuth2
86
91
  end
87
92
 
88
93
  # Makes a request relative to the specified site root.
94
+ # Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616),
95
+ # allowing the use of relative URLs in Location headers.
96
+ # @see https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2
89
97
  #
90
98
  # @param [Symbol] verb one of :get, :post, :put, :delete
91
99
  # @param [String] url URL path of request
@@ -97,16 +105,19 @@ module OAuth2
97
105
  # code response for this request. Will default to client option
98
106
  # @option opts [Symbol] :parse @see Response::initialize
99
107
  # @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
-
108
+ def request(verb, url, opts = {})
103
109
  url = connection.build_url(url).to_s
104
110
 
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?
111
+ begin
112
+ response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
113
+ req.params.update(opts[:params]) if opts[:params]
114
+ yield(req) if block_given?
115
+ end
116
+ rescue Faraday::ConnectionFailed => e
117
+ raise ConnectionError, e
108
118
  end
109
- response = Response.new(response, :parse => opts[:parse])
119
+
120
+ response = Response.new(response, parse: opts[:parse])
110
121
 
111
122
  case response.status
112
123
  when 301, 302, 303, 307
@@ -118,7 +129,14 @@ module OAuth2
118
129
  verb = :get
119
130
  opts.delete(:body)
120
131
  end
121
- request(verb, response.headers['location'], opts)
132
+ location = response.headers['location']
133
+ if location
134
+ full_location = response.response.env.url.merge(location)
135
+ request(verb, full_location, opts)
136
+ else
137
+ error = Error.new(response)
138
+ raise(error, "Got #{response.status} status code, but no Location header was present")
139
+ end
122
140
  when 200..299, 300..399
123
141
  # on non-redirecting 3xx statuses, just return the response
124
142
  response
@@ -126,7 +144,6 @@ module OAuth2
126
144
  error = Error.new(response)
127
145
  raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
128
146
 
129
- response.error = error
130
147
  response
131
148
  else
132
149
  error = Error.new(response)
@@ -136,22 +153,22 @@ module OAuth2
136
153
 
137
154
  # Initializes an AccessToken by making a request to the token endpoint
138
155
  #
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
156
+ # @param params [Hash] a Hash of params for the token endpoint
157
+ # @param access_token_opts [Hash] access token options, to pass to the AccessToken object
158
+ # @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED)
159
+ # @param access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken, @version 2.0+
142
160
  # @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
161
+ def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token], access_token_class: AccessToken)
144
162
  params = params.map do |key, value|
145
163
  if RESERVED_PARAM_KEYS.include?(key)
146
164
  [key.to_sym, value]
147
165
  else
148
166
  [key, value]
149
167
  end
150
- end
151
- params = Hash[params]
168
+ end.to_h
152
169
 
153
- params = Authenticator.new(id, secret, options[:auth_scheme]).apply(params)
154
- opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
170
+ params = authenticator.apply(params)
171
+ opts = {raise_errors: options[:raise_errors], parse: params.delete(:parse)}
155
172
  headers = params.delete(:headers) || {}
156
173
  if options[:token_method] == :post
157
174
  opts[:body] = params
@@ -161,45 +178,44 @@ module OAuth2
161
178
  opts[:headers] = {}
162
179
  end
163
180
  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
181
+ http_method = options[:token_method]
182
+ http_method = :post if http_method == :post_with_query_string
183
+ response = request(http_method, token_url, opts)
171
184
 
172
- if options[:raise_errors] && !access_token
173
- error = Error.new(response)
174
- raise(error)
185
+ # In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
186
+ # We preserve this behavior here, but a custom access_token_class that implements #from_hash
187
+ # should be used instead.
188
+ if extract_access_token
189
+ parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
190
+ else
191
+ parse_response(response, access_token_opts, access_token_class)
175
192
  end
176
- access_token
177
193
  end
178
194
 
179
195
  # The Authorization Code strategy
180
196
  #
181
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
197
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
182
198
  def auth_code
183
199
  @auth_code ||= OAuth2::Strategy::AuthCode.new(self)
184
200
  end
185
201
 
186
202
  # The Implicit strategy
187
203
  #
188
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
204
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
189
205
  def implicit
190
206
  @implicit ||= OAuth2::Strategy::Implicit.new(self)
191
207
  end
192
208
 
193
209
  # The Resource Owner Password Credentials strategy
194
210
  #
195
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
211
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
196
212
  def password
197
213
  @password ||= OAuth2::Strategy::Password.new(self)
198
214
  end
199
215
 
200
216
  # The Client Credentials strategy
201
217
  #
202
- # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
218
+ # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.4
203
219
  def client_credentials
204
220
  @client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
205
221
  end
@@ -219,10 +235,10 @@ module OAuth2
219
235
  #
220
236
  # @api semipublic
221
237
  #
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
238
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1
239
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
240
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
241
+ # @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
226
242
  # @return [Hash] the params to add to a request or URL
227
243
  def redirection_params
228
244
  if options[:redirect_uri]
@@ -232,26 +248,61 @@ module OAuth2
232
248
  end
233
249
  end
234
250
 
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)
251
+ private
252
+
253
+ # Returns the authenticator object
254
+ #
255
+ # @return [Authenticator] the initialized Authenticator
256
+ def authenticator
257
+ Authenticator.new(id, secret, options[:auth_scheme])
238
258
  end
239
259
 
240
- private
260
+ def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
261
+ access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
241
262
 
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)
263
+ return access_token if access_token
245
264
 
246
- hash = parsed_response.merge(access_token_opts)
265
+ if options[:raise_errors]
266
+ error = Error.new(response)
267
+ raise(error)
268
+ end
247
269
 
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)
270
+ nil
271
+ end
272
+
273
+ def parse_response(response, access_token_opts, access_token_class)
274
+ data = response.parsed
275
+
276
+ unless data.is_a?(Hash) && access_token_class.contains_token?(data)
277
+ return unless options[:raise_errors]
278
+
279
+ error = Error.new(response)
280
+ raise(error)
254
281
  end
282
+
283
+ build_access_token(response, access_token_opts, access_token_class)
284
+ end
285
+
286
+ # Builds the access token from the response of the HTTP call
287
+ #
288
+ # @return [AccessToken] the initialized AccessToken
289
+ def build_access_token(response, access_token_opts, access_token_class)
290
+ access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token|
291
+ access_token.response = response if access_token.respond_to?(:response=)
292
+ end
293
+ end
294
+
295
+ # Builds the access token from the response of the HTTP call with legacy extract_access_token
296
+ #
297
+ # @return [AccessToken] the initialized AccessToken
298
+ def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
299
+ extract_access_token.call(self, response.parsed.merge(access_token_opts))
300
+ rescue StandardError
301
+ nil
302
+ end
303
+
304
+ def oauth_debug_logging(builder)
305
+ builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true'
255
306
  end
256
307
  end
257
308
  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