oauth2 1.4.7 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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