oauth2 1.4.7 → 2.0.1

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 = {}, 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