oauth2 2.0.18 → 2.0.23

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.
data/SECURITY.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  | Version | Supported |
6
6
  |----------|-----------|
7
- | 1.latest | ✅ |
7
+ | 2.0.latest | ✅ |
8
8
 
9
9
  ## Security contact information
10
10
 
@@ -12,8 +12,6 @@ To report a security vulnerability, please use the
12
12
  [Tidelift security contact](https://tidelift.com/security).
13
13
  Tidelift will coordinate the fix and disclosure.
14
14
 
15
- More detailed explanation of the process is in [IRP.md][IRP]
16
-
17
15
  ## Additional Support
18
16
 
19
17
  If you are interested in support for versions older than the latest release,
@@ -21,4 +19,3 @@ please consider sponsoring the project / maintainer @ https://liberapay.com/pbol
21
19
  or find other sponsorship links in the [README].
22
20
 
23
21
  [README]: README.md
24
- [IRP]: IRP.md
data/certs/pboling.pem ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl
3
+ ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW
4
+ A2NvbTAeFw0yNTA1MDQxNTMzMDlaFw00NTA0MjkxNTMzMDlaMEMxFTATBgNVBAMM
5
+ DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy
6
+ LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAruUoo0WA
7
+ uoNuq6puKWYeRYiZekz/nsDeK5x/0IEirzcCEvaHr3Bmz7rjo1I6On3gGKmiZs61
8
+ LRmQ3oxy77ydmkGTXBjruJB+pQEn7UfLSgQ0xa1/X3kdBZt6RmabFlBxnHkoaGY5
9
+ mZuZ5+Z7walmv6sFD9ajhzj+oIgwWfnEHkXYTR8I6VLN7MRRKGMPoZ/yvOmxb2DN
10
+ coEEHWKO9CvgYpW7asIihl/9GMpKiRkcYPm9dGQzZc6uTwom1COfW0+ZOFrDVBuV
11
+ FMQRPswZcY4Wlq0uEBLPU7hxnCL9nKK6Y9IhdDcz1mY6HZ91WImNslOSI0S8hRpj
12
+ yGOWxQIhBT3fqCBlRIqFQBudrnD9jSNpSGsFvbEijd5ns7Z9ZMehXkXDycpGAUj1
13
+ to/5cuTWWw1JqUWrKJYoifnVhtE1o1DZ+LkPtWxHtz5kjDG/zR3MG0Ula0UOavlD
14
+ qbnbcXPBnwXtTFeZ3C+yrWpE4pGnl3yGkZj9SMTlo9qnTMiPmuWKQDatAgMBAAGj
15
+ fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQE8uWvNbPVNRXZ
16
+ HlgPbc2PCzC4bjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG
17
+ A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD
18
+ ggGBAJbnUwfJQFPkBgH9cL7hoBfRtmWiCvdqdjeTmi04u8zVNCUox0A4gT982DE9
19
+ wmuN12LpdajxZONqbXuzZvc+nb0StFwmFYZG6iDwaf4BPywm2e/Vmq0YG45vZXGR
20
+ L8yMDSK1cQXjmA+ZBKOHKWavxP6Vp7lWvjAhz8RFwqF9GuNIdhv9NpnCAWcMZtpm
21
+ GUPyIWw/Cw/2wZp74QzZj6Npx+LdXoLTF1HMSJXZ7/pkxLCsB8m4EFVdb/IrW/0k
22
+ kNSfjtAfBHO8nLGuqQZVH9IBD1i9K6aSs7pT6TW8itXUIlkIUI2tg5YzW6OFfPzq
23
+ QekSkX3lZfY+HTSp/o+YvKkqWLUV7PQ7xh1ZYDtocpaHwgxe/j3bBqHE+CUPH2vA
24
+ 0V/FwdTRWcwsjVoOJTrYcff8pBZ8r2MvtAc54xfnnhGFzeRHfcltobgFxkAXdE6p
25
+ DVjBtqT23eugOqQ73umLcYDZkc36vnqGxUBSsXrzY9pzV5gGr2I8YUxMqf6ATrZt
26
+ L9nRqA==
27
+ -----END CERTIFICATE-----
@@ -57,28 +57,19 @@ module OAuth2
57
57
  def from_hash(client, hash)
58
58
  fresh = hash.dup
59
59
  # If token_name is present, then use that key name
60
- key =
61
- if fresh.key?(:token_name)
62
- t_key = fresh[:token_name]
63
- no_tokens_warning(fresh, t_key)
64
- t_key
65
- else
66
- # Otherwise, if one of the supported default keys is present, use whichever has precedence
67
- supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
68
- t_key = supported_keys[0]
69
- extra_tokens_warning(supported_keys, t_key)
70
- t_key
71
- end
72
- # :nocov:
73
- # TODO: Get rid of this branching logic when dropping Hashie < v3.2
74
- token = if !defined?(Hashie::VERSION) # i.e. <= "1.1.0"; the first Hashie to ship with a VERSION constant
75
- warn("snaky_hash and oauth2 will drop support for Hashie v0 in the next major version. Please upgrade to a modern Hashie.")
76
- # There is a bug in Hashie v0, which is accounts for.
77
- fresh.delete(key) || fresh[key] || ""
60
+ if fresh.key?(:token_name)
61
+ t_key = fresh[:token_name]
62
+ no_tokens_warning(fresh, t_key)
78
63
  else
79
- fresh.delete(key) || ""
64
+ # Otherwise, if one of the supported default keys is present, use whichever has precedence
65
+ supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
66
+ t_key = supported_keys[0]
67
+ extra_tokens_warning(supported_keys, t_key)
80
68
  end
81
69
  # :nocov:
70
+ # TODO: Get rid of this branching logic when dropping Hashie < v3.2
71
+ token = extract_token_value(fresh, t_key)
72
+ # :nocov:
82
73
  new(client, token, fresh)
83
74
  end
84
75
 
@@ -111,6 +102,17 @@ Custom token_name (#{key}) is not found in (#{hash.keys})
111
102
  You may need to set `snaky: false`. See inline documentation for more info.
112
103
  ])
113
104
  end
105
+
106
+ # :nocov:
107
+ def extract_token_value(fresh, key)
108
+ token_value = fresh.delete(key)
109
+ return token_value || "" if defined?(Hashie::VERSION)
110
+
111
+ warn("snaky_hash and oauth2 will drop support for Hashie v0 in the next major version. Please upgrade to a modern Hashie.")
112
+ # There is a bug in Hashie v0, which this accounts for.
113
+ token_value || fresh[key] || ""
114
+ end
115
+ # :nocov:
114
116
  end
115
117
 
116
118
  # Initialize an AccessToken
@@ -308,8 +310,8 @@ You may need to set `snaky: false`. See inline documentation for more info.
308
310
  # TODO: Switch when dropping Ruby < 2.5 support
309
311
  # params.transform_keys(&:to_sym) # Ruby 2.5 only
310
312
  # Old Ruby transform_keys alternative:
311
- sheesh = @params.each_with_object({}) { |(k, v), memo|
312
- memo[k.to_sym] = v
313
+ sheesh = @params.each_with_object({}) { |(key, value), memo|
314
+ memo[key.to_sym] = value
313
315
  }
314
316
  sheesh.merge(hsh)
315
317
  end
@@ -378,6 +380,7 @@ You may need to set `snaky: false`. See inline documentation for more info.
378
380
 
379
381
  def configure_authentication!(opts, verb)
380
382
  mode_opt = options[:mode]
383
+ param_name = options[:param_name]
381
384
  mode =
382
385
  if mode_opt.respond_to?(:call)
383
386
  mode_opt.call(verb)
@@ -391,19 +394,19 @@ You may need to set `snaky: false`. See inline documentation for more info.
391
394
 
392
395
  case mode
393
396
  when :header
394
- opts[:headers] ||= {}
395
- opts[:headers].merge!(headers)
397
+ request_headers = opts[:headers] ||= {}
398
+ request_headers.merge!(headers)
396
399
  when :query
397
400
  # OAuth 2.1 note: Bearer tokens in the query string are omitted from the spec due to security risks.
398
401
  # Prefer the default :header mode whenever possible.
399
- opts[:params] ||= {}
400
- opts[:params][options[:param_name]] = token
402
+ request_params = opts[:params] ||= {}
403
+ request_params[param_name] = token
401
404
  when :body
402
- opts[:body] ||= {}
403
- if opts[:body].is_a?(Hash)
404
- opts[:body][options[:param_name]] = token
405
+ request_body = opts[:body] ||= {}
406
+ if request_body.is_a?(Hash)
407
+ request_body[param_name] = token
405
408
  else
406
- opts[:body] += "&#{options[:param_name]}=#{token}"
409
+ opts[:body] = "#{request_body}&#{param_name}=#{token}"
407
410
  end
408
411
  # @todo support for multi-part (file uploads)
409
412
  else
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OAuth2
4
+ AUTH_SANITIZER = begin
5
+ auth_sanitizer_requirement = Gem::Requirement.new("~> 0.2", ">= 0.2.1")
6
+ auth_sanitizer_spec = Gem.loaded_specs["auth-sanitizer"]
7
+ unless auth_sanitizer_spec && auth_sanitizer_requirement.satisfied_by?(auth_sanitizer_spec.version)
8
+ # :nocov:
9
+ auth_sanitizer_spec = Gem::Specification.find_by_name("auth-sanitizer", auth_sanitizer_requirement)
10
+ # :nocov:
11
+ end
12
+
13
+ auth_sanitizer_loader_path = File.join(
14
+ auth_sanitizer_spec.full_gem_path,
15
+ "lib/auth_sanitizer/loader.rb"
16
+ )
17
+ unless File.file?(auth_sanitizer_loader_path)
18
+ # :nocov:
19
+ raise LoadError, "oauth2 requires auth-sanitizer #{auth_sanitizer_requirement}; " \
20
+ "loader not found at #{auth_sanitizer_loader_path}"
21
+ # :nocov:
22
+ end
23
+
24
+ auth_sanitizer_loader_namespace = Module.new
25
+ auth_sanitizer_loader_namespace.module_eval(
26
+ File.read(auth_sanitizer_loader_path),
27
+ auth_sanitizer_loader_path,
28
+ 1
29
+ )
30
+
31
+ auth_sanitizer_loader_namespace.
32
+ const_get(:AuthSanitizer).
33
+ const_get(:Loader).
34
+ load_isolated
35
+ end
36
+ end
@@ -51,13 +51,15 @@ module OAuth2
51
51
  end
52
52
  end
53
53
 
54
- # Encodes a Basic Authorization header value for the provided credentials.
55
- #
56
- # @param [String] user The client identifier
57
- # @param [String] password The client secret
58
- # @return [String] The value to use for the Authorization header
59
- def self.encode_basic_auth(user, password)
60
- "Basic #{Base64.strict_encode64("#{user}:#{password}")}"
54
+ class << self
55
+ # Encodes a Basic Authorization header value for the provided credentials.
56
+ #
57
+ # @param [String] user The client identifier
58
+ # @param [String] password The client secret
59
+ # @return [String] The value to use for the Authorization header
60
+ def encode_basic_auth(user, password)
61
+ "Basic #{Base64.strict_encode64("#{user}:#{password}")}"
62
+ end
61
63
  end
62
64
 
63
65
  private
data/lib/oauth2/client.rb CHANGED
@@ -42,7 +42,7 @@ module OAuth2
42
42
  # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday
43
43
  # @option options [Boolean] :raise_errors (true) whether to raise an OAuth2::Error on responses with 400+ status codes
44
44
  # @option options [Integer] :max_redirects (5) maximum number of redirects to follow
45
- # @option options [Logger] :logger (::Logger.new($stdout)) Logger instance for HTTP request/response output; requires OAUTH_DEBUG to be true
45
+ # @option options [Logger] :logger (::Logger.new($stdout)) Logger instance for HTTP request/response output; requires OAUTH_DEBUG to be true. When debug logging is enabled, sensitive values are filtered using {OAuth2::AUTH_SANITIZER::SanitizedLogger} initialized from `OAuth2.config[:filtered_label]` and the key names in `OAuth2.config[:filtered_debug_keys]`.
46
46
  # @option options [Class] :access_token_class (AccessToken) class to use for access tokens; you can subclass OAuth2::AccessToken, @version 2.0+
47
47
  # @option options [Hash] :ssl SSL options for Faraday
48
48
  #
@@ -86,8 +86,9 @@ module OAuth2
86
86
  @connection ||=
87
87
  Faraday.new(site, options[:connection_opts]) do |builder|
88
88
  oauth_debug_logging(builder)
89
- if options[:connection_build]
90
- options[:connection_build].call(builder)
89
+ connection_build = options[:connection_build]
90
+ if connection_build
91
+ connection_build.call(builder)
91
92
  else
92
93
  builder.request(:url_encoded) # form-encode POST params
93
94
  builder.adapter(Faraday.default_adapter) # make requests with Net::HTTP
@@ -149,9 +150,9 @@ module OAuth2
149
150
 
150
151
  case status
151
152
  when 301, 302, 303, 307
152
- req_opts[:redirect_count] ||= 0
153
- req_opts[:redirect_count] += 1
154
- return response if req_opts[:redirect_count] > options[:max_redirects]
153
+ redirect_count = (req_opts[:redirect_count] || 0).to_i + 1
154
+ req_opts[:redirect_count] = redirect_count
155
+ return response if redirect_count > options[:max_redirects]
155
156
 
156
157
  if status == 303
157
158
  verb = :get
@@ -159,8 +160,9 @@ module OAuth2
159
160
  end
160
161
  location = response.headers["location"]
161
162
  if location
162
- full_location = response.response.env.url.merge(location)
163
- request(verb, full_location, req_opts)
163
+ current_location = response.response.env.url
164
+ full_location = resolve_redirect_location(current_location, location)
165
+ request(verb, full_location, sanitize_redirect_options(req_opts, current_location, full_location))
164
166
  else
165
167
  error = Error.new(response)
166
168
  raise(error, "Got #{status} status code, but no Location header was present")
@@ -337,8 +339,9 @@ module OAuth2
337
339
  #
338
340
  # @return [Hash] the params to add to a request or URL
339
341
  def redirection_params
340
- if options[:redirect_uri]
341
- {"redirect_uri" => options[:redirect_uri]}
342
+ redirect_uri = options[:redirect_uri]
343
+ if redirect_uri
344
+ {"redirect_uri" => redirect_uri}
342
345
  else
343
346
  {}
344
347
  end
@@ -445,18 +448,18 @@ module OAuth2
445
448
  url = connection.build_url(url).to_s
446
449
  # See: Hash#partition https://bugs.ruby-lang.org/issues/16252
447
450
  req_opts, oauth_opts = opts.
448
- partition { |k, _v| RESERVED_REQ_KEYS.include?(k.to_s) }.
449
- map { |p| Hash[p] }
451
+ partition { |key, _value| RESERVED_REQ_KEYS.include?(key.to_s) }.
452
+ map(&:to_h)
450
453
 
451
454
  begin
452
455
  response = connection.run_request(verb, url, req_opts[:body], req_opts[:headers]) do |req|
453
456
  req.params.update(req_opts[:params]) if req_opts[:params]
454
457
  yield(req) if block_given?
455
458
  end
456
- rescue Faraday::ConnectionFailed => e
457
- raise ConnectionError, e
458
- rescue Faraday::TimeoutError => e
459
- raise TimeoutError, e
459
+ rescue Faraday::ConnectionFailed => exception
460
+ raise ConnectionError, exception
461
+ rescue Faraday::TimeoutError => exception
462
+ raise TimeoutError, exception
460
463
  end
461
464
 
462
465
  parse = oauth_opts.key?(:parse) ? oauth_opts.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
@@ -465,6 +468,49 @@ module OAuth2
465
468
  Response.new(response, parse: parse, snaky: snaky)
466
469
  end
467
470
 
471
+ def resolve_redirect_location(current_location, location)
472
+ return protocol_relative_redirect_location(current_location, location) if location.respond_to?(:start_with?) && location.start_with?("//")
473
+
474
+ current_location.merge(location)
475
+ end
476
+
477
+ def protocol_relative_redirect_location(current_location, location)
478
+ protocol_relative_location = URI.parse(location)
479
+ authority = +""
480
+ authority << "#{protocol_relative_location.userinfo}@" if protocol_relative_location.userinfo
481
+ authority << protocol_relative_location.host.to_s
482
+ authority << ":#{protocol_relative_location.port}" if protocol_relative_location.port
483
+
484
+ current_location.dup.tap do |safe_location|
485
+ safe_location.path = "///#{authority}#{protocol_relative_location.path}"
486
+ safe_location.query = protocol_relative_location.query if safe_location.respond_to?(:query=)
487
+ safe_location.fragment = protocol_relative_location.fragment if safe_location.respond_to?(:fragment=)
488
+ end
489
+ end
490
+
491
+ def sanitize_redirect_options(req_opts, current_location, next_location)
492
+ return req_opts unless cross_origin_redirect?(current_location, next_location)
493
+
494
+ headers = req_opts[:headers]
495
+ return req_opts unless headers && headers.any? { |key, _value| authorization_header?(key) }
496
+
497
+ safe_opts = req_opts.dup
498
+ safe_headers = headers.dup
499
+ safe_headers.delete_if { |key, _value| authorization_header?(key) }
500
+ safe_opts[:headers] = safe_headers
501
+ safe_opts
502
+ end
503
+
504
+ def authorization_header?(key)
505
+ key.to_s.casecmp("Authorization").zero?
506
+ end
507
+
508
+ def cross_origin_redirect?(current_location, next_location)
509
+ current_location.scheme != next_location.scheme ||
510
+ current_location.host != next_location.host ||
511
+ current_location.port != next_location.port
512
+ end
513
+
468
514
  # Returns the authenticator object
469
515
  #
470
516
  # @return [Authenticator] the initialized Authenticator
@@ -563,7 +609,18 @@ module OAuth2
563
609
  end
564
610
 
565
611
  def oauth_debug_logging(builder)
566
- builder.response(:logger, options[:logger], bodies: true) if OAuth2::OAUTH_DEBUG
612
+ if OAuth2::OAUTH_DEBUG
613
+ config = OAuth2.config
614
+ builder.response(
615
+ :logger,
616
+ OAuth2::AUTH_SANITIZER::SanitizedLogger.new(
617
+ options[:logger],
618
+ filtered_keys: config[:filtered_debug_keys],
619
+ label: config[:filtered_label]
620
+ ),
621
+ bodies: true
622
+ )
623
+ end
567
624
  end
568
625
  end
569
626
  end
data/lib/oauth2/error.rb CHANGED
@@ -17,10 +17,13 @@ module OAuth2
17
17
  # @param [OAuth2::Response, Hash, Object] response A Response or error payload
18
18
  def initialize(response)
19
19
  @response = response
20
+ @code = nil
21
+ @description = nil
20
22
  if response.respond_to?(:parsed)
21
- if response.parsed.is_a?(Hash)
22
- @code = response.parsed["error"]
23
- @description = response.parsed["error_description"]
23
+ parsed_response = response.parsed
24
+ if parsed_response.is_a?(Hash)
25
+ @code = parsed_response["error"]
26
+ @description = parsed_response["error_description"]
24
27
  end
25
28
  elsif response.is_a?(Hash)
26
29
  @code = response["error"]
@@ -44,11 +47,12 @@ module OAuth2
44
47
  # @return [String] Message suitable for StandardError
45
48
  def error_message(response_body, opts = {})
46
49
  lines = []
50
+ error_description = opts[:error_description]
47
51
 
48
- lines << opts[:error_description] if opts[:error_description]
52
+ lines << error_description if error_description
49
53
 
50
- error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
51
- script_encoding = opts[:error_description].encoding
54
+ error_string = if response_body.respond_to?(:encode) && error_description.respond_to?(:encoding)
55
+ script_encoding = error_description.encoding
52
56
  response_body.encode(script_encoding, invalid: :replace, undef: :replace)
53
57
  else
54
58
  response_body
@@ -1,52 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OAuth2
2
- # Mixin that redacts sensitive instance variables in #inspect output.
4
+ # Permanent alias for {OAuth2::AUTH_SANITIZER::FilteredAttributes}.
3
5
  #
4
- # Classes include this module and declare which attributes should be filtered
5
- # using {.filtered_attributes}. Any instance variable name that includes one of
6
- # those attribute names will be shown as [FILTERED] in the object's inspect.
7
- module FilteredAttributes
8
- # Hook invoked when the module is included. Extends the including class with
9
- # class-level helpers.
10
- #
11
- # @param [Class] base The including class
12
- # @return [void]
13
- def self.included(base)
14
- base.extend(ClassMethods)
15
- end
16
-
17
- # Class-level helpers for configuring filtered attributes.
18
- module ClassMethods
19
- # Declare attributes that should be redacted in inspect output.
20
- #
21
- # @param [Array<Symbol, String>] attributes One or more attribute names
22
- # @return [void]
23
- def filtered_attributes(*attributes)
24
- @filtered_attribute_names = attributes.map(&:to_sym)
25
- end
26
-
27
- # The configured attribute names to filter.
28
- #
29
- # @return [Array<Symbol>]
30
- def filtered_attribute_names
31
- @filtered_attribute_names || []
32
- end
33
- end
34
-
35
- # Custom inspect that redacts configured attributes.
36
- #
37
- # @return [String]
38
- def inspect
39
- filtered_attribute_names = self.class.filtered_attribute_names
40
- return super if filtered_attribute_names.empty?
41
-
42
- inspected_vars = instance_variables.map do |var|
43
- if filtered_attribute_names.any? { |filtered_var| var.to_s.include?(filtered_var.to_s) }
44
- "#{var}=[FILTERED]"
45
- else
46
- "#{var}=#{instance_variable_get(var).inspect}"
47
- end
48
- end
49
- "#<#{self.class}:#{object_id} #{inspected_vars.join(", ")}>"
50
- end
51
- end
6
+ # This constant is intentionally kept in the `OAuth2` namespace because it
7
+ # was part of the public API before the implementation was extracted into the
8
+ # `auth-sanitizer` gem. It will **not** be deprecated or removed.
9
+ FilteredAttributes = OAuth2::AUTH_SANITIZER::FilteredAttributes
52
10
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require "set"
4
5
  require "multi_xml"
5
6
  require "rack"
6
7
 
@@ -43,18 +44,20 @@ module OAuth2
43
44
  "text/plain" => :text,
44
45
  }
45
46
 
46
- # Adds a new content type parser.
47
- #
48
- # @param [Symbol] key A descriptive symbol key such as :json or :query
49
- # @param [Array<String>, String] mime_types One or more mime types to which this parser applies
50
- # @yield [String] Block that will be called to parse the response body
51
- # @yieldparam [String] body The response body to parse
52
- # @return [void]
53
- def self.register_parser(key, mime_types, &block)
54
- key = key.to_sym
55
- @@parsers[key] = block
56
- Array(mime_types).each do |mime_type|
57
- @@content_types[mime_type] = key
47
+ class << self
48
+ # Adds a new content type parser.
49
+ #
50
+ # @param [Symbol] key A descriptive symbol key such as :json or :query
51
+ # @param [Array<String>, String] mime_types One or more mime types to which this parser applies
52
+ # @yield [String] Block that will be called to parse the response body
53
+ # @yieldparam [String] body The response body to parse
54
+ # @return [void]
55
+ def register_parser(key, mime_types, &block)
56
+ key = key.to_sym
57
+ @@parsers[key] = block
58
+ Array(mime_types).each do |mime_type|
59
+ @@content_types[mime_type] = key
60
+ end
58
61
  end
59
62
  end
60
63
 
@@ -106,15 +109,16 @@ module OAuth2
106
109
  def parsed
107
110
  return @parsed if defined?(@parsed)
108
111
 
112
+ response_parser = parser
109
113
  @parsed =
110
- if parser.respond_to?(:call)
111
- case parser.arity
114
+ if response_parser.respond_to?(:call)
115
+ case response_parser.arity
112
116
  when 0
113
- parser.call
117
+ response_parser.call
114
118
  when 1
115
- parser.call(body)
119
+ response_parser.call(body)
116
120
  else
117
- parser.call(body, response)
121
+ response_parser.call(body, response)
118
122
  end
119
123
  end
120
124
 
@@ -130,9 +134,10 @@ module OAuth2
130
134
  #
131
135
  # @return [String, nil] The content type or nil if headers are not present
132
136
  def content_type
133
- return unless response.headers
137
+ response_headers = response.headers
138
+ return unless response_headers
134
139
 
135
- ((response.headers.values_at("content-type", "Content-Type").compact.first || "").split(";").first || "").strip.downcase
140
+ ((response_headers.values_at("content-type", "Content-Type").compact.first || "").split(";").first || "").strip.downcase
136
141
  end
137
142
 
138
143
  # Determines the parser to be used for the response body
@@ -152,11 +157,12 @@ module OAuth2
152
157
  def parser
153
158
  return @parser if defined?(@parser)
154
159
 
160
+ parse_option = options[:parse]
155
161
  @parser =
156
- if options[:parse].respond_to?(:call)
157
- options[:parse]
158
- elsif options[:parse]
159
- @@parsers[options[:parse].to_sym]
162
+ if parse_option.respond_to?(:call)
163
+ parse_option
164
+ elsif parse_option
165
+ @@parsers[parse_option.to_sym]
160
166
  end
161
167
 
162
168
  @parser ||= @@parsers[@@content_types[content_type]]
@@ -169,7 +175,7 @@ end
169
175
  OAuth2::Response.register_parser(:xml, ["text/xml", "application/rss+xml", "application/rdf+xml", "application/atom+xml", "application/xml"]) do |body|
170
176
  next body unless body.respond_to?(:to_str)
171
177
 
172
- MultiXml.parse(body)
178
+ (defined?(MultiXML) ? MultiXML : MultiXml).parse(body)
173
179
  end
174
180
 
175
181
  # Register JSON parser
@@ -2,6 +2,7 @@
2
2
 
3
3
  module OAuth2
4
4
  module Version
5
- VERSION = "2.0.18"
5
+ VERSION = "2.0.23"
6
6
  end
7
+ VERSION = Version::VERSION # Traditional Constant Location
7
8
  end
data/lib/oauth2.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # includes modules from stdlib
4
- require "cgi"
4
+ require "cgi/escape"
5
5
  require "time"
6
6
 
7
7
  # third party gems
@@ -10,6 +10,7 @@ require "version_gem"
10
10
 
11
11
  # includes gem files
12
12
  require_relative "oauth2/version"
13
+ require_relative "oauth2/auth_sanitizer"
13
14
  require_relative "oauth2/filtered_attributes"
14
15
  require_relative "oauth2/error"
15
16
  require_relative "oauth2/authenticator"
@@ -43,38 +44,59 @@ module OAuth2
43
44
  # config[:silence_no_tokens_warning] = false
44
45
  # end
45
46
  #
47
+ # @example Customize filtered output markers and debug-log value filtering by key name
48
+ # OAuth2.configure do |config|
49
+ # config[:filtered_label] = "[REDACTED]"
50
+ # config[:filtered_debug_keys] += ["client_assertion"]
51
+ # end
52
+ #
53
+ # Existing objects and logger wrappers snapshot filtering configuration during
54
+ # initialization. Changing these config values later affects only newly
55
+ # initialized objects and debug loggers.
56
+ #
46
57
  # @return [SnakyHash::SymbolKeyed] A mutable Hash-like config with symbol keys
47
58
  DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(
48
59
  silence_extra_tokens_warning: true,
49
60
  silence_no_tokens_warning: true,
61
+ filtered_label: "[FILTERED]",
62
+ filtered_debug_keys: %w[
63
+ access_token
64
+ refresh_token
65
+ id_token
66
+ client_secret
67
+ assertion
68
+ code_verifier
69
+ token
70
+ ]
50
71
  )
51
72
 
52
73
  # The current runtime configuration for the library.
53
74
  #
54
75
  # @return [SnakyHash::SymbolKeyed]
55
- @config = DEFAULT_CONFIG.dup
76
+ CONFIG = DEFAULT_CONFIG.dup
56
77
 
57
78
  class << self
58
- # Access the current configuration.
79
+ def config
80
+ CONFIG
81
+ end
82
+
83
+ # Configure global library behavior.
59
84
  #
60
- # Prefer using {OAuth2.configure} to mutate configuration.
85
+ # Yields the mutable configuration object so callers can update settings.
61
86
  #
62
- # @return [SnakyHash::SymbolKeyed]
63
- attr_reader :config
87
+ # @yieldparam [SnakyHash::SymbolKeyed] config the configuration object
88
+ # @return [void]
89
+ def configure
90
+ yield config
91
+ end
64
92
  end
65
-
66
- # Configure global library behavior.
67
- #
68
- # Yields the mutable configuration object so callers can update settings.
69
- #
70
- # @yieldparam [SnakyHash::SymbolKeyed] config the configuration object
71
- # @return [void]
72
- def configure
73
- yield @config
74
- end
75
- module_function :configure
76
93
  end
77
94
 
95
+ # Wire OAuth2::AUTH_SANITIZER's label provider to read from OAuth2.config so that
96
+ # FilteredAttributes-bearing objects and OAuth2::AUTH_SANITIZER::SanitizedLogger instances
97
+ # pick up OAuth2.config[:filtered_label] at their initialization time.
98
+ OAuth2::AUTH_SANITIZER.filtered_label_provider = -> { OAuth2.config[:filtered_label] }
99
+
78
100
  # Extend OAuth2::Version with VersionGem helpers to provide semantic version helpers.
79
101
  OAuth2::Version.class_eval do
80
102
  extend VersionGem::Basic