oauth 1.1.0 → 1.1.6

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -0
  3. data/CHANGELOG.md +637 -269
  4. data/CITATION.cff +20 -0
  5. data/CODE_OF_CONDUCT.md +79 -29
  6. data/CONTRIBUTING.md +256 -24
  7. data/FUNDING.md +74 -0
  8. data/LICENSE.md +71 -0
  9. data/README.md +615 -287
  10. data/RUBOCOP.md +71 -0
  11. data/SECURITY.md +11 -16
  12. data/certs/pboling.pem +27 -0
  13. data/lib/oauth/auth_sanitizer.rb +36 -0
  14. data/lib/oauth/client/action_controller_request.rb +22 -13
  15. data/lib/oauth/client/em_http.rb +106 -99
  16. data/lib/oauth/client/helper.rb +15 -11
  17. data/lib/oauth/client/net_http.rb +39 -13
  18. data/lib/oauth/consumer.rb +194 -75
  19. data/lib/oauth/errors/problem.rb +1 -1
  20. data/lib/oauth/helper.rb +25 -3
  21. data/lib/oauth/oauth.rb +28 -6
  22. data/lib/oauth/optional.rb +20 -0
  23. data/lib/oauth/request_proxy/action_controller_request.rb +11 -7
  24. data/lib/oauth/request_proxy/action_dispatch_request.rb +34 -0
  25. data/lib/oauth/request_proxy/base.rb +38 -27
  26. data/lib/oauth/request_proxy/em_http_request.rb +53 -52
  27. data/lib/oauth/request_proxy/jabber_request.rb +9 -2
  28. data/lib/oauth/request_proxy/net_http.rb +1 -1
  29. data/lib/oauth/request_proxy/rack_request.rb +0 -4
  30. data/lib/oauth/request_proxy/rest_client_request.rb +4 -3
  31. data/lib/oauth/request_proxy.rb +17 -13
  32. data/lib/oauth/server.rb +12 -8
  33. data/lib/oauth/signature/base.rb +15 -5
  34. data/lib/oauth/signature/rsa/sha1.rb +11 -4
  35. data/lib/oauth/signature.rb +43 -39
  36. data/lib/oauth/tokens/access_token.rb +1 -1
  37. data/lib/oauth/tokens/consumer_token.rb +8 -6
  38. data/lib/oauth/tokens/request_token.rb +9 -4
  39. data/lib/oauth/tokens/token.rb +13 -1
  40. data/lib/oauth/version.rb +2 -1
  41. data/lib/oauth.rb +2 -0
  42. data/sig/oauth/consumer.rbs +9 -0
  43. data/sig/oauth/signature/base.rbs +12 -0
  44. data/sig/oauth/tokens/token.rbs +8 -0
  45. data/sig/oauth/version.rbs +6 -0
  46. data.tar.gz.sig +0 -0
  47. metadata +316 -79
  48. metadata.gz.sig +0 -0
  49. data/LICENSE +0 -22
  50. data/TODO +0 -32
@@ -8,7 +8,22 @@ require "oauth/errors"
8
8
  require "cgi"
9
9
 
10
10
  module OAuth
11
+ # Consumer credentials and request configuration for OAuth 1.0 / 1.0a flows.
12
+ #
13
+ # Includes {OAuth::AUTH_SANITIZER::FilteredAttributes} so inspect output redacts the
14
+ # consumer secret while leaving non-sensitive configuration visible.
11
15
  class Consumer
16
+ include OAuth::AUTH_SANITIZER::FilteredAttributes
17
+
18
+ # Instance attributes exposed by the consumer.
19
+ #
20
+ # @!attribute [rw] options
21
+ # @return [Hash] Consumer configuration options
22
+ # @!attribute [rw] key
23
+ # @return [String] OAuth consumer key
24
+ # @!attribute [rw] secret
25
+ # @return [String] OAuth consumer secret (redacted in `#inspect`)
26
+
12
27
  # determine the certificate authority path to verify SSL certs
13
28
  if ENV["SSL_CERT_FILE"]
14
29
  if File.exist?(ENV["SSL_CERT_FILE"])
@@ -19,8 +34,11 @@ module OAuth
19
34
  end
20
35
 
21
36
  unless defined?(CA_FILE)
22
- CA_FILES = %w[/etc/ssl/certs/ca-certificates.crt /etc/pki/tls/certs/ca-bundle.crt
23
- /usr/share/curl/curl-ca-bundle.crt].freeze
37
+ CA_FILES = %w[
38
+ /etc/ssl/certs/ca-certificates.crt
39
+ /etc/pki/tls/certs/ca-bundle.crt
40
+ /usr/share/curl/curl-ca-bundle.crt
41
+ ].freeze
24
42
  CA_FILES.each do |ca_file|
25
43
  if File.exist?(ca_file)
26
44
  CA_FILE = ca_file
@@ -70,12 +88,19 @@ module OAuth
70
88
  # spec. Possible values are true and false
71
89
  body_hash_enabled: true,
72
90
 
73
- oauth_version: "1.0"
74
- }
91
+ oauth_version: "1.0",
92
+
93
+ # Token endpoint redirects are followed only within the same origin by
94
+ # default. Cross-origin redirects can re-sign token requests for an
95
+ # attacker-controlled endpoint, so they require explicit opt-in.
96
+ token_request_max_redirects: 10,
97
+ token_request_cross_origin_redirects: false,
98
+ },
75
99
  )
76
100
 
77
101
  attr_accessor :options, :key, :secret
78
- attr_writer :site, :http
102
+ filtered_attributes :secret
103
+ attr_writer :site, :http
79
104
 
80
105
  # Create a new consumer instance by passing it a configuration hash:
81
106
  #
@@ -101,7 +126,7 @@ module OAuth
101
126
  # @photos=@access_token.get('/photos.xml')
102
127
  #
103
128
  def initialize(consumer_key, consumer_secret, options = {})
104
- @key = consumer_key
129
+ @key = consumer_key
105
130
  @secret = consumer_secret
106
131
 
107
132
  # ensure that keys are symbols
@@ -116,12 +141,12 @@ module OAuth
116
141
 
117
142
  def debug_output
118
143
  @debug_output ||= case @options[:debug_output]
119
- when nil, false
120
- when true
121
- $stdout
122
- else
123
- @options[:debug_output]
124
- end
144
+ when nil, false
145
+ when true
146
+ $stdout
147
+ else
148
+ @options[:debug_output]
149
+ end
125
150
  end
126
151
 
127
152
  # The HTTP object for the site. The HTTP Object is what you get when you do Net::HTTP.new
@@ -132,51 +157,93 @@ module OAuth
132
157
  # Contains the root URI for this site
133
158
  def uri(custom_uri = nil)
134
159
  if custom_uri
135
- @uri = custom_uri
160
+ @uri = custom_uri
136
161
  @http = create_http # yike, oh well. less intrusive this way
137
162
  else # if no custom passed, we use existing, which, if unset, is set to site uri
138
163
  @uri ||= URI.parse(site)
139
164
  end
140
165
  end
141
166
 
167
+ # Exchanges a verified Request Token for an Access Token.
168
+ #
169
+ # OAuth 1.0 vs 1.0a:
170
+ # - 1.0a requires including oauth_verifier (as returned by the Provider after
171
+ # user authorization) when performing this exchange in a 3‑legged flow.
172
+ # - 1.0 flows did not include oauth_verifier.
173
+ #
174
+ # Usage (3‑legged):
175
+ # access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier])
176
+ #
177
+ # @param request_token [OAuth::RequestToken] The authorized request token
178
+ # @param request_options [Hash] OAuth or request options (include :oauth_verifier for 1.0a)
179
+ # @param arguments [Array] Optional POST body and headers
180
+ # @yield [response_body] If a block is given, yields the raw response body.
181
+ # @return [OAuth::AccessToken]
142
182
  def get_access_token(request_token, request_options = {}, *arguments, &block)
143
- response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token,
144
- request_options, *arguments, &block)
183
+ response = token_request(
184
+ http_method,
185
+ (access_token_url? ? access_token_url : access_token_path),
186
+ request_token,
187
+ request_options,
188
+ *arguments,
189
+ &block
190
+ )
145
191
  OAuth::AccessToken.from_hash(self, response)
146
192
  end
147
193
 
148
194
  # Makes a request to the service for a new OAuth::RequestToken
149
195
  #
150
- # @request_token = @consumer.get_request_token
196
+ # Example:
197
+ # @request_token = @consumer.get_request_token
151
198
  #
152
199
  # To include OAuth parameters:
153
- #
154
- # @request_token = @consumer.get_request_token \
155
- # :oauth_callback => "http://example.com/cb"
200
+ # @request_token = @consumer.get_request_token(
201
+ # oauth_callback: "http://example.com/cb"
202
+ # )
156
203
  #
157
204
  # To include application-specific parameters:
205
+ # @request_token = @consumer.get_request_token({}, foo: "bar")
158
206
  #
159
- # @request_token = @consumer.get_request_token({}, :foo => "bar")
207
+ # OAuth 1.0 vs 1.0a:
208
+ # - In 1.0a, the Consumer SHOULD send oauth_callback when obtaining a request token
209
+ # (or explicitly use OUT_OF_BAND) and the Provider MUST include
210
+ # oauth_callback_confirmed=true in the response.
211
+ # - This library defaults oauth_callback to OUT_OF_BAND ("oob") when not provided,
212
+ # which works for both 1.0 and 1.0a, and mirrors common provider behavior.
213
+ # - The oauth_callback_confirmed response is parsed by the token classes; it is not
214
+ # part of the signature base string and thus is not signed.
160
215
  #
161
- # TODO oauth_callback should be a mandatory parameter
216
+ # TODO: In a future major release, oauth_callback may be made mandatory unless
217
+ # request_options[:exclude_callback] is set, to reflect 1.0a guidance.
218
+ #
219
+ # @param request_options [Hash] OAuth options for the request. Notably
220
+ # :oauth_callback can be set to a URL, or OAuth::OUT_OF_BAND ("oob").
221
+ # @param arguments [Array] Optional POST body and headers
222
+ # @yield [response_body] If a block is given, yields the raw response body.
223
+ # @return [OAuth::RequestToken]
162
224
  def get_request_token(request_options = {}, *arguments, &block)
163
225
  # if oauth_callback wasn't provided, it is assumed that oauth_verifiers
164
226
  # will be exchanged out of band
165
227
  request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback]
166
228
 
167
229
  response = if block
168
- token_request(
169
- http_method,
170
- (request_token_url? ? request_token_url : request_token_path),
171
- nil,
172
- request_options,
173
- *arguments,
174
- &block
175
- )
176
- else
177
- token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil,
178
- request_options, *arguments)
179
- end
230
+ token_request(
231
+ http_method,
232
+ (request_token_url? ? request_token_url : request_token_path),
233
+ nil,
234
+ request_options,
235
+ *arguments,
236
+ &block
237
+ )
238
+ else
239
+ token_request(
240
+ http_method,
241
+ (request_token_url? ? request_token_url : request_token_path),
242
+ nil,
243
+ request_options,
244
+ *arguments,
245
+ )
246
+ end
180
247
  OAuth::RequestToken.from_hash(self, response)
181
248
  end
182
249
 
@@ -191,23 +258,23 @@ module OAuth
191
258
  # @consumer.request(:post, '/people', @token, {}, @person.to_xml, { 'Content-Type' => 'application/xml' })
192
259
  #
193
260
  def request(http_method, path, token = nil, request_options = {}, *arguments)
194
- unless %r{^/}.match?(path)
261
+ unless %r{^/} =~ path
195
262
  @http = create_http(path)
196
- _uri = URI.parse(path)
197
- path = "#{_uri.path}#{_uri.query ? "?#{_uri.query}" : ""}"
263
+ uri = URI.parse(path)
264
+ path = "#{uri.path}#{"?#{uri.query}" if uri.query}"
198
265
  end
199
266
 
200
267
  # override the request with your own, this is useful for file uploads which Net::HTTP does not do
201
268
  req = create_signed_request(http_method, path, token, request_options, *arguments)
202
- return nil if block_given? && (yield(req) == :done)
269
+ return if block_given? && (yield(req) == :done)
203
270
 
204
271
  rsp = http.request(req)
205
272
  # check for an error reported by the Problem Reporting extension
206
273
  # (https://wiki.oauth.net/ProblemReporting)
207
274
  # note: a 200 may actually be an error; check for an oauth_problem key to be sure
208
275
  if !(headers = rsp.to_hash["www-authenticate"]).nil? &&
209
- (h = headers.grep(/^OAuth /)).any? &&
210
- h.first.include?("oauth_problem")
276
+ (h = headers.grep(/^OAuth /)).any? &&
277
+ h.first.include?("oauth_problem")
211
278
 
212
279
  # puts "Header: #{h.first}"
213
280
 
@@ -233,37 +300,34 @@ module OAuth
233
300
  end
234
301
 
235
302
  # Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
236
- def token_request(http_method, path, token = nil, request_options = {}, *arguments)
237
- request_options[:token_request] ||= true
238
- response = request(http_method, path, token, request_options, *arguments)
303
+ def token_request(http_method, path, token = nil, request_options = {}, *arguments, &block)
304
+ response = request(http_method, path, token, token_request_options(request_options), *arguments)
239
305
  case response.code.to_i
240
306
 
241
307
  when (200..299)
242
- if block_given?
243
- yield response.body
308
+ if block
309
+ block.call(response.body)
244
310
  else
245
311
  # symbolize keys
246
312
  # TODO this could be considered unexpected behavior; symbols or not?
247
313
  # TODO this also drops subsequent values from multi-valued keys
248
314
  CGI.parse(response.body).each_with_object({}) do |(k, v), h|
249
315
  h[k.strip.to_sym] = v.first
250
- h[k.strip] = v.first
316
+ h[k.strip] = v.first
251
317
  end
252
318
  end
253
319
  when (300..399)
254
- # Parse redirect to follow
255
- uri = URI.parse(response["location"])
256
- our_uri = URI.parse(site)
320
+ current_uri = token_request_uri(path)
321
+ redirected_uri = token_request_redirect_uri(current_uri, response)
322
+ response.error! unless redirected_uri
257
323
 
258
- # Guard against infinite redirects
259
- response.error! if uri.path == path && our_uri.host == uri.host
260
-
261
- if uri.path == path && our_uri.host != uri.host
262
- options[:site] = "#{uri.scheme}://#{uri.host}"
263
- @http = create_http
264
- end
324
+ redirect_count = request_options[:token_request_redirect_count].to_i + 1
325
+ response.error! if redirect_count > token_request_max_redirects(request_options)
326
+ response.error! if token_request_cross_origin?(current_uri, redirected_uri) &&
327
+ !token_request_cross_origin_redirects?(request_options)
265
328
 
266
- token_request(http_method, uri.path, token, request_options, arguments)
329
+ redirect_options = request_options.merge(token_request_redirect_count: redirect_count)
330
+ token_request(http_method, token_request_redirect_path(current_uri, redirected_uri), token, redirect_options, *arguments, &block)
267
331
  when (400..499)
268
332
  raise OAuth::Unauthorized, response
269
333
  else
@@ -271,6 +335,55 @@ module OAuth
271
335
  end
272
336
  end
273
337
 
338
+ def token_request_options(request_options)
339
+ request_options.merge(token_request: true).tap do |options|
340
+ options.delete(:token_request_redirect_count)
341
+ options.delete(:token_request_max_redirects)
342
+ options.delete(:token_request_cross_origin_redirects)
343
+ end
344
+ end
345
+
346
+ def token_request_uri(path)
347
+ uri = URI.parse(path)
348
+ return uri if uri.absolute?
349
+
350
+ URI.parse(site).merge(path)
351
+ end
352
+
353
+ def token_request_redirect_uri(current_uri, response)
354
+ location = response["location"]
355
+ return if location.nil? || location.to_s.empty?
356
+
357
+ current_uri.merge(location)
358
+ end
359
+
360
+ def token_request_redirect_path(current_uri, redirected_uri)
361
+ return redirected_uri.to_s if token_request_cross_origin?(current_uri, redirected_uri)
362
+
363
+ redirected_uri.request_uri
364
+ end
365
+
366
+ def token_request_max_redirects(request_options)
367
+ request_options[:token_request_max_redirects] || options[:token_request_max_redirects]
368
+ end
369
+
370
+ def token_request_cross_origin_redirects?(request_options)
371
+ request_options.fetch(:token_request_cross_origin_redirects, options[:token_request_cross_origin_redirects])
372
+ end
373
+
374
+ def token_request_cross_origin?(current_uri, redirected_uri)
375
+ current_uri.scheme.to_s.downcase != redirected_uri.scheme.to_s.downcase ||
376
+ current_uri.host.to_s.downcase != redirected_uri.host.to_s.downcase ||
377
+ token_request_effective_port(current_uri) != token_request_effective_port(redirected_uri)
378
+ end
379
+
380
+ def token_request_effective_port(uri)
381
+ return uri.port if uri.port
382
+ return 443 if uri.scheme == "https"
383
+
384
+ 80 if uri.scheme == "http"
385
+ end
386
+
274
387
  # Sign the Request object. Use this if you have an externally generated http request object you want to sign.
275
388
  def sign!(request, token = nil, request_options = {})
276
389
  request.oauth!(http, self, token, options.merge(request_options))
@@ -286,7 +399,7 @@ module OAuth
286
399
  end
287
400
 
288
401
  def request_endpoint
289
- return nil if @options[:request_endpoint].nil?
402
+ return if @options[:request_endpoint].nil?
290
403
 
291
404
  @options[:request_endpoint].to_s
292
405
  end
@@ -351,28 +464,34 @@ module OAuth
351
464
  protected
352
465
 
353
466
  # Instantiates the http object
354
- def create_http(_url = nil)
355
- _url = request_endpoint unless request_endpoint.nil?
356
-
357
- our_uri = if _url.nil? || _url[0] =~ %r{^/}
358
- URI.parse(site)
359
- else
360
- your_uri = URI.parse(_url)
361
- if your_uri.host.nil?
362
- # If the _url is a path, missing the leading slash, then it won't have a host,
363
- # and our_uri *must* have a host, so we parse site instead.
364
- URI.parse(site)
365
- else
366
- your_uri
367
- end
368
- end
467
+ def create_http(url = nil)
468
+ url = request_endpoint unless request_endpoint.nil?
469
+
470
+ our_uri = if url.nil? || url[0] =~ %r{^/}
471
+ URI.parse(site)
472
+ else
473
+ your_uri = URI.parse(url)
474
+ if your_uri.host.nil?
475
+ # If the _url is a path, missing the leading slash, then it won't have a host,
476
+ # and our_uri *must* have a host, so we parse site instead.
477
+ URI.parse(site)
478
+ else
479
+ your_uri
480
+ end
481
+ end
369
482
 
370
483
  if proxy.nil?
371
484
  http_object = Net::HTTP.new(our_uri.host, our_uri.port)
372
485
  else
373
486
  proxy_uri = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
374
- http_object = Net::HTTP.new(our_uri.host, our_uri.port, proxy_uri.host, proxy_uri.port, proxy_uri.user,
375
- proxy_uri.password)
487
+ http_object = Net::HTTP.new(
488
+ our_uri.host,
489
+ our_uri.port,
490
+ proxy_uri.host,
491
+ proxy_uri.port,
492
+ proxy_uri.user,
493
+ proxy_uri.password,
494
+ )
376
495
  end
377
496
 
378
497
  http_object.use_ssl = (our_uri.scheme == "https")
@@ -453,7 +572,7 @@ module OAuth
453
572
  end
454
573
 
455
574
  def marshal_dump(*_args)
456
- { key: @key, secret: @secret, options: @options }
575
+ {key: @key, secret: @secret, options: @options}
457
576
  end
458
577
 
459
578
  def marshal_load(data)
@@ -7,7 +7,7 @@ module OAuth
7
7
  def initialize(problem, request = nil, params = {})
8
8
  super(request)
9
9
  @problem = problem
10
- @params = params
10
+ @params = params
11
11
  end
12
12
 
13
13
  def to_s
data/lib/oauth/helper.rb CHANGED
@@ -18,11 +18,33 @@ module OAuth
18
18
  end
19
19
 
20
20
  def _escape(string)
21
- URI::DEFAULT_PARSER.escape(string, OAuth::RESERVED_CHARACTERS)
21
+ # Percent-encode per RFC 3986 (unreserved: A-Z a-z 0-9 - . _ ~)
22
+ # Encode by byte to ensure stable behavior across Ruby versions and encodings.
23
+ bytes = string.to_s.b.bytes
24
+ bytes.map do |b|
25
+ ch = b.chr
26
+ if ch =~ OAuth::RESERVED_CHARACTERS
27
+ "%%%02X" % b
28
+ else
29
+ ch
30
+ end
31
+ end.join
22
32
  end
23
33
 
24
34
  def unescape(value)
25
- URI::DEFAULT_PARSER.unescape(value.gsub("+", "%2B"))
35
+ # Do NOT treat "+" as space; OAuth treats '+' as a literal plus unless percent-encoded.
36
+ str = value.to_s.gsub("+", "%2B")
37
+ # Decode %HH sequences; leave malformed sequences intact.
38
+ decoded = str.gsub(/%([0-9A-Fa-f]{2})/) { Regexp.last_match(1).to_i(16).chr }
39
+ # Prefer UTF-8 when the decoded bytes form valid UTF-8; otherwise, return as binary.
40
+ begin
41
+ utf8 = decoded.dup
42
+ utf8.force_encoding(Encoding::UTF_8)
43
+ decoded = utf8 if utf8.valid_encoding?
44
+ rescue NameError
45
+ # Older Rubies without Encoding constants: keep original encoding.
46
+ end
47
+ decoded
26
48
  end
27
49
 
28
50
  # Generate a random key of up to +size+ bytes. The value returned is Base64 encoded with non-word
@@ -31,7 +53,7 @@ module OAuth
31
53
  Base64.encode64(OpenSSL::Random.random_bytes(size)).gsub(/\W/, "")
32
54
  end
33
55
 
34
- alias generate_nonce generate_key
56
+ alias_method :generate_nonce, :generate_key
35
57
 
36
58
  def generate_timestamp # :nodoc:
37
59
  Time.now.to_i.to_s
data/lib/oauth/oauth.rb CHANGED
@@ -1,14 +1,36 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OAuth
4
- # request tokens are passed between the consumer and the provider out of
5
- # band (i.e. callbacks cannot be used), per section 6.1.1
4
+ # Out-Of-Band callback token value.
5
+ # OAuth 1.0 and 1.0a both support out-of-band flows, where callbacks cannot be used.
6
+ # See RFC 5849 (OAuth 1.0), Section 6.1.1: Obtaining an Unauthorized Request Token
7
+ # and the 1.0a errata. Providers treating "oob" as the callback URL indicate that
8
+ # the verifier (for 1.0a) will be communicated out of band to the Consumer.
6
9
  OUT_OF_BAND = "oob"
7
10
 
8
- # required parameters, per sections 6.1.1, 6.3.1, and 7
9
- PARAMETERS = %w[oauth_callback oauth_consumer_key oauth_token
10
- oauth_signature_method oauth_timestamp oauth_nonce oauth_verifier
11
- oauth_version oauth_signature oauth_body_hash].freeze
11
+ # OAuth parameter keys this library recognizes when normalizing/signing requests.
12
+ # Notes on 1.0 vs 1.0a:
13
+ # - oauth_verifier: Introduced by OAuth 1.0a. Returned to the Consumer after user
14
+ # authorization and required when exchanging a Request Token for an Access Token
15
+ # (Section 6.3.1 in RFC 5849 / 1.0a change).
16
+ # - oauth_callback: Present in 1.0; 1.0a clarified that the Consumer MUST send it when
17
+ # obtaining a Request Token (or use "oob") and that the Service Provider MUST return
18
+ # oauth_callback_confirmed=true with the Request Token response to prevent session
19
+ # fixation attacks. Note that oauth_callback_confirmed is a response parameter, not
20
+ # a request signing parameter, and thus is not listed here.
21
+ # Other keys are common to both 1.0 and 1.0a.
22
+ PARAMETERS = %w[
23
+ oauth_callback
24
+ oauth_consumer_key
25
+ oauth_token
26
+ oauth_signature_method
27
+ oauth_timestamp
28
+ oauth_nonce
29
+ oauth_verifier
30
+ oauth_version
31
+ oauth_signature
32
+ oauth_body_hash
33
+ ].freeze
12
34
 
13
35
  # reserved character regexp, per section 5.1
14
36
  RESERVED_CHARACTERS = /[^a-zA-Z0-9\-._~]/.freeze
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OAuth
4
+ # Helpers for optional, lazily loaded integrations.
5
+ module Optional
6
+ class << self
7
+ # Try to load EventMachine HTTP client support provided by em-http-request.
8
+ #
9
+ # Returns true if available, false if the dependency is not installed.
10
+ # Never raises LoadError.
11
+ def em_http_available?
12
+ # em-http-request provides "em-http" entrypoint
13
+ require "em-http"
14
+ true
15
+ rescue LoadError
16
+ false
17
+ end
18
+ end
19
+ end
20
+ end
@@ -16,14 +16,18 @@ module OAuth
16
16
  end
17
17
 
18
18
  def uri
19
- request.url
19
+ options[:uri] || request.url
20
20
  end
21
21
 
22
22
  def parameters
23
23
  if options[:clobber_request]
24
24
  options[:parameters] || {}
25
25
  else
26
- params = request_params.merge(query_params).merge(header_params)
26
+ # Rails proxies should expose array-style values for params to align with
27
+ # historical oauth gem behavior / specs. Header params remain scalars.
28
+ rq = wrap_values(request_params)
29
+ qq = wrap_values(query_params)
30
+ params = rq.merge(qq).merge(header_params)
27
31
  params.stringify_keys! if params.respond_to?(:stringify_keys!)
28
32
  params.merge(options[:parameters] || {})
29
33
  end
@@ -43,11 +47,11 @@ module OAuth
43
47
  params << request.raw_post if raw_post_signature?
44
48
  end
45
49
 
46
- params.
47
- join("&").split("&").
48
- reject { |s| s.match(/\A\s*\z/) }.
49
- map { |p| p.split("=").map { |esc| CGI.unescape(esc) } }.
50
- reject { |kv| kv[0] == "oauth_signature" }
50
+ params
51
+ .join("&").split("&")
52
+ .reject { |s| s.match(/\A\s*\z/) }
53
+ .map { |p| p.split("=").map { |esc| CGI.unescape(esc) } }
54
+ .reject { |kv| kv[0] == "oauth_signature" }
51
55
  end
52
56
 
53
57
  def raw_post_signature?
@@ -6,6 +6,40 @@ module OAuth
6
6
  module RequestProxy
7
7
  class ActionDispatchRequest < OAuth::RequestProxy::RackRequest
8
8
  proxies ::ActionDispatch::Request
9
+
10
+ # Prefer the explicitly provided URI, which carries scheme/host info
11
+ # when ActionDispatch env may be minimal in tests.
12
+ def uri
13
+ options[:uri] || super
14
+ end
15
+
16
+ # Rails' ActionDispatch proxy should expose array-style parameters
17
+ # for request/query params to align with legacy oauth gem expectations.
18
+ def parameters
19
+ if options[:clobber_request]
20
+ options[:parameters] || {}
21
+ else
22
+ rq = wrap_values(request_params)
23
+ qq = wrap_values(query_params)
24
+ params = rq.merge(qq).merge(header_params)
25
+ params.merge(options[:parameters] || {})
26
+ end
27
+ end
28
+
29
+ protected
30
+
31
+ def query_params
32
+ # ActionDispatch::Request responds to GET
33
+ request.GET
34
+ end
35
+
36
+ def request_params
37
+ if request.content_type && request.content_type.to_s.downcase.start_with?("application/x-www-form-urlencoded")
38
+ request.POST
39
+ else
40
+ {}
41
+ end
42
+ end
9
43
  end
10
44
  end
11
45
  end