oauth2 2.0.9 → 2.0.22

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/lib/oauth2/error.rb CHANGED
@@ -1,55 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OAuth2
4
+ # Represents an OAuth2 error condition.
5
+ #
6
+ # Wraps details from an OAuth2::Response or Hash payload returned by an
7
+ # authorization server, exposing error code and description per RFC 6749.
4
8
  class Error < StandardError
9
+ # @return [OAuth2::Response, Hash, Object] Original response or payload used to build the error
10
+ # @return [String] Raw body content (if available)
11
+ # @return [String, nil] Error code (e.g., 'invalid_grant')
12
+ # @return [String, nil] Human-readable description for the error
5
13
  attr_reader :response, :body, :code, :description
6
14
 
7
- # standard error codes include:
8
- # 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
9
- # response might be a Response object, or the response.parsed hash
15
+ # Create a new OAuth2::Error
16
+ #
17
+ # @param [OAuth2::Response, Hash, Object] response A Response or error payload
10
18
  def initialize(response)
11
19
  @response = response
20
+ @code = nil
21
+ @description = nil
12
22
  if response.respond_to?(:parsed)
13
23
  if response.parsed.is_a?(Hash)
14
- @code = response.parsed['error']
15
- @description = response.parsed['error_description']
24
+ @code = response.parsed["error"]
25
+ @description = response.parsed["error_description"]
16
26
  end
17
27
  elsif response.is_a?(Hash)
18
- @code = response['error']
19
- @description = response['error_description']
28
+ @code = response["error"]
29
+ @description = response["error_description"]
20
30
  end
21
31
  @body = if response.respond_to?(:body)
22
- response.body
23
- else
24
- @response
25
- end
32
+ response.body
33
+ else
34
+ @response
35
+ end
26
36
  message_opts = parse_error_description(@code, @description)
27
37
  super(error_message(@body, message_opts))
28
38
  end
29
39
 
30
40
  private
31
41
 
42
+ # Builds a multi-line error message including description and raw body.
43
+ #
44
+ # @param [String, #encode] response_body Response body content
45
+ # @param [Hash] opts Options including :error_description
46
+ # @return [String] Message suitable for StandardError
32
47
  def error_message(response_body, opts = {})
33
48
  lines = []
34
49
 
35
50
  lines << opts[:error_description] if opts[:error_description]
36
51
 
37
52
  error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
38
- script_encoding = opts[:error_description].encoding
39
- response_body.encode(script_encoding, invalid: :replace, undef: :replace)
40
- else
41
- response_body
42
- end
53
+ script_encoding = opts[:error_description].encoding
54
+ response_body.encode(script_encoding, invalid: :replace, undef: :replace)
55
+ else
56
+ response_body
57
+ end
43
58
 
44
59
  lines << error_string
45
60
 
46
61
  lines.join("\n")
47
62
  end
48
63
 
64
+ # Formats the OAuth2 error code and description into a single string.
65
+ #
66
+ # @param [String, nil] code OAuth2 error code
67
+ # @param [String, nil] description OAuth2 error description
68
+ # @return [Hash] Options hash containing :error_description when present
49
69
  def parse_error_description(code, description)
50
70
  return {} unless code || description
51
71
 
52
- error_description = ''
72
+ error_description = ""
53
73
  error_description += "#{code}: " if code
54
74
  error_description += description if description
55
75
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OAuth2
4
+ # Permanent alias for {OAuth2::AUTH_SANITIZER::FilteredAttributes}.
5
+ #
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
10
+ end
@@ -1,80 +1,110 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'multi_xml'
5
- require 'rack'
3
+ require "json"
4
+ require "multi_xml"
5
+ require "rack"
6
6
 
7
7
  module OAuth2
8
- # OAuth2::Response class
8
+ # The Response class handles HTTP responses in the OAuth2 gem, providing methods
9
+ # to access and parse response data in various formats.
10
+ #
11
+ # @since 1.0.0
9
12
  class Response
13
+ # Default configuration options for Response instances
14
+ #
15
+ # @return [Hash] The default options hash
10
16
  DEFAULT_OPTIONS = {
11
17
  parse: :automatic,
12
18
  snaky: true,
19
+ snaky_hash_klass: SnakyHash::StringKeyed,
13
20
  }.freeze
21
+
22
+ # @return [Faraday::Response] The raw Faraday response object
14
23
  attr_reader :response
24
+
25
+ # @return [Hash] The options hash for this instance
15
26
  attr_accessor :options
16
27
 
17
- # Procs that, when called, will parse a response body according
18
- # to the specified format.
28
+ # @private
29
+ # Storage for response body parser procedures
30
+ #
31
+ # @return [Hash<Symbol, Proc>] Hash of parser procs keyed by format symbol
19
32
  @@parsers = {
20
33
  query: ->(body) { Rack::Utils.parse_query(body) },
21
34
  text: ->(body) { body },
22
35
  }
23
36
 
24
- # Content type assignments for various potential HTTP content types.
37
+ # @private
38
+ # Maps content types to parser symbols
39
+ #
40
+ # @return [Hash<String, Symbol>] Hash of content types mapped to parser symbols
25
41
  @@content_types = {
26
- 'application/x-www-form-urlencoded' => :query,
27
- 'text/plain' => :text,
42
+ "application/x-www-form-urlencoded" => :query,
43
+ "text/plain" => :text,
28
44
  }
29
45
 
30
- # Adds a new content type parser.
31
- #
32
- # @param [Symbol] key A descriptive symbol key such as :json or :query.
33
- # @param [Array] mime_types One or more mime types to which this parser applies.
34
- # @yield [String] A block returning parsed content.
35
- def self.register_parser(key, mime_types, &block)
36
- key = key.to_sym
37
- @@parsers[key] = block
38
- Array(mime_types).each do |mime_type|
39
- @@content_types[mime_type] = key
46
+ class << self
47
+ # Adds a new content type parser.
48
+ #
49
+ # @param [Symbol] key A descriptive symbol key such as :json or :query
50
+ # @param [Array<String>, String] mime_types One or more mime types to which this parser applies
51
+ # @yield [String] Block that will be called to parse the response body
52
+ # @yieldparam [String] body The response body to parse
53
+ # @return [void]
54
+ def register_parser(key, mime_types, &block)
55
+ key = key.to_sym
56
+ @@parsers[key] = block
57
+ Array(mime_types).each do |mime_type|
58
+ @@content_types[mime_type] = key
59
+ end
40
60
  end
41
61
  end
42
62
 
43
63
  # Initializes a Response instance
44
64
  #
45
65
  # @param [Faraday::Response] response The Faraday response instance
46
- # @param [Symbol] parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded),
47
- # :json, or :automatic (determined by Content-Type response header)
48
- # @param [true, false] snaky (true) Convert @parsed to a snake-case,
49
- # indifferent-access SnakyHash::StringKeyed, which is a subclass of Hashie::Mash (from hashie gem)?
50
- # @param [Hash] options all other options for initializing the instance
51
- def initialize(response, parse: :automatic, snaky: true, **options)
66
+ # @param [Symbol] parse (:automatic) How to parse the response body
67
+ # @param [Boolean] snaky (true) Whether to convert parsed response to snake_case using SnakyHash
68
+ # @param [Class, nil] snaky_hash_klass (nil) Custom class for snake_case hash conversion
69
+ # @param [Hash] options Additional options for the response
70
+ # @option options [Symbol] :parse (:automatic) Parse strategy (:query, :json, or :automatic)
71
+ # @option options [Boolean] :snaky (true) Enable/disable snake_case conversion
72
+ # @option options [Class] :snaky_hash_klass (SnakyHash::StringKeyed) Class to use for hash conversion
73
+ # @return [OAuth2::Response] The new Response instance
74
+ def initialize(response, parse: :automatic, snaky: true, snaky_hash_klass: nil, **options)
52
75
  @response = response
53
76
  @options = {
54
77
  parse: parse,
55
78
  snaky: snaky,
79
+ snaky_hash_klass: snaky_hash_klass,
56
80
  }.merge(options)
57
81
  end
58
82
 
59
83
  # The HTTP response headers
84
+ #
85
+ # @return [Hash] The response headers
60
86
  def headers
61
87
  response.headers
62
88
  end
63
89
 
64
90
  # The HTTP response status code
91
+ #
92
+ # @return [Integer] The response status code
65
93
  def status
66
94
  response.status
67
95
  end
68
96
 
69
97
  # The HTTP response body
98
+ #
99
+ # @return [String] The response body or empty string if nil
70
100
  def body
71
- response.body || ''
101
+ response.body || ""
72
102
  end
73
103
 
74
- # The {#response} {#body} as parsed by {#parser}.
104
+ # The parsed response body
75
105
  #
76
- # @return [Object] As returned by {#parser} if it is #call-able.
77
- # @return [nil] If the {#parser} is not #call-able.
106
+ # @return [Object, SnakyHash::StringKeyed] The parsed response body
107
+ # @return [nil] If no parser is available
78
108
  def parsed
79
109
  return @parsed if defined?(@parsed)
80
110
 
@@ -90,34 +120,37 @@ module OAuth2
90
120
  end
91
121
  end
92
122
 
93
- @parsed = SnakyHash::StringKeyed.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)
123
+ if options[:snaky] && @parsed.is_a?(Hash)
124
+ hash_klass = options[:snaky_hash_klass] || DEFAULT_OPTIONS[:snaky_hash_klass]
125
+ @parsed = hash_klass[@parsed]
126
+ end
94
127
 
95
128
  @parsed
96
129
  end
97
130
 
98
- # Attempts to determine the content type of the response.
131
+ # Determines the content type of the response
132
+ #
133
+ # @return [String, nil] The content type or nil if headers are not present
99
134
  def content_type
100
- return nil unless response.headers
135
+ return unless response.headers
101
136
 
102
- ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip.downcase
137
+ ((response.headers.values_at("content-type", "Content-Type").compact.first || "").split(";").first || "").strip.downcase
103
138
  end
104
139
 
105
- # Determines the parser (a Proc or other Object which responds to #call)
106
- # that will be passed the {#body} (and optional {#response}) to supply
107
- # {#parsed}.
140
+ # Determines the parser to be used for the response body
108
141
  #
109
- # The parser can be supplied as the +:parse+ option in the form of a Proc
110
- # (or other Object responding to #call) or a Symbol. In the latter case,
111
- # the actual parser will be looked up in {@@parsers} by the supplied Symbol.
142
+ # @note The parser can be supplied as the +:parse+ option in the form of a Proc
143
+ # (or other Object responding to #call) or a Symbol. In the latter case,
144
+ # the actual parser will be looked up in {@@parsers} by the supplied Symbol.
112
145
  #
113
- # If no +:parse+ option is supplied, the lookup Symbol will be determined
114
- # by looking up {#content_type} in {@@content_types}.
146
+ # @note If no +:parse+ option is supplied, the lookup Symbol will be determined
147
+ # by looking up {#content_type} in {@@content_types}.
115
148
  #
116
- # If {#parser} is a Proc, it will be called with no arguments, just
117
- # {#body}, or {#body} and {#response}, depending on the Proc's arity.
149
+ # @note If {#parser} is a Proc, it will be called with no arguments, just
150
+ # {#body}, or {#body} and {#response}, depending on the Proc's arity.
118
151
  #
119
- # @return [Proc, #call] If a parser was found.
120
- # @return [nil] If no parser was found.
152
+ # @return [Proc, #call] The parser proc or callable object
153
+ # @return [nil] If no suitable parser is found
121
154
  def parser
122
155
  return @parser if defined?(@parser)
123
156
 
@@ -133,16 +166,21 @@ module OAuth2
133
166
  end
134
167
  end
135
168
 
136
- OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body|
169
+ # Register XML parser
170
+ # @api private
171
+ OAuth2::Response.register_parser(:xml, ["text/xml", "application/rss+xml", "application/rdf+xml", "application/atom+xml", "application/xml"]) do |body|
137
172
  next body unless body.respond_to?(:to_str)
138
173
 
139
174
  MultiXml.parse(body)
140
175
  end
141
176
 
142
- OAuth2::Response.register_parser(:json, ['application/json', 'text/javascript', 'application/hal+json', 'application/vnd.collection+json', 'application/vnd.api+json', 'application/problem+json']) do |body|
177
+ # Register JSON parser
178
+ # @api private
179
+ OAuth2::Response.register_parser(:json, ["application/json", "text/javascript", "application/hal+json", "application/vnd.collection+json", "application/vnd.api+json", "application/problem+json"]) do |body|
143
180
  next body unless body.respond_to?(:to_str)
144
181
 
145
- body = body.dup.force_encoding(::Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
182
+ body = body.dup.force_encoding(Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
183
+ next body if body.respond_to?(:empty?) && body.empty?
146
184
 
147
- ::JSON.parse(body)
185
+ JSON.parse(body)
148
186
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'jwt'
3
+ require "jwt"
4
4
 
5
5
  module OAuth2
6
6
  module Strategy
@@ -34,7 +34,7 @@ module OAuth2
34
34
  #
35
35
  # @raise [NotImplementedError]
36
36
  def authorize_url
37
- raise(NotImplementedError, 'The authorization endpoint is not used in this strategy')
37
+ raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
38
38
  end
39
39
 
40
40
  # Retrieve an access token given the specified client.
@@ -66,8 +66,8 @@ module OAuth2
66
66
  # @see https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
67
67
  #
68
68
  # The object type of `:key` may depend on the value of `:algorithm`. Sample arguments:
69
- # get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'})
70
- # get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')})
69
+ # `get_token(claim_set, {:algorithm => 'HS256', :key => 'secret_key'})`
70
+ # `get_token(claim_set, {:algorithm => 'RS256', :key => OpenSSL::PKCS12.new(File.read('my_key.p12'), 'not_secret')})`
71
71
  #
72
72
  # @param [Hash] request_opts options that will be used to assemble the request
73
73
  # @option request_opts [String] :scope the url parameter `scope` that may be required by some endpoints
@@ -87,15 +87,18 @@ module OAuth2
87
87
 
88
88
  def build_request(assertion, request_opts = {})
89
89
  {
90
- grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
90
+ grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
91
91
  assertion: assertion,
92
92
  }.merge(request_opts)
93
93
  end
94
94
 
95
95
  def build_assertion(claims, encoding_opts)
96
- raise ArgumentError.new(message: 'Please provide an encoding_opts hash with :algorithm and :key') if !encoding_opts.is_a?(Hash) || (%i[algorithm key] - encoding_opts.keys).any?
96
+ raise ArgumentError.new(message: "Please provide an encoding_opts hash with :algorithm and :key") if !encoding_opts.is_a?(Hash) || (%i[algorithm key] - encoding_opts.keys).any?
97
97
 
98
- JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm])
98
+ headers = {}
99
+ headers[:kid] = encoding_opts[:kid] if encoding_opts.key?(:kid)
100
+
101
+ JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm], headers)
99
102
  end
100
103
  end
101
104
  end
@@ -4,13 +4,23 @@ module OAuth2
4
4
  module Strategy
5
5
  # The Authorization Code Strategy
6
6
  #
7
+ # OAuth 2.1 notes:
8
+ # - PKCE is required for all OAuth clients using the authorization code flow (especially public clients).
9
+ # This library does not enforce PKCE generation/verification; implement PKCE in your application when required.
10
+ # - Redirect URIs must be compared using exact string matching by the Authorization Server.
11
+ # This client forwards redirect_uri but does not perform server-side validation.
12
+ #
13
+ # References:
14
+ # - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
15
+ # - OAuth for native apps (RFC 8252) and PKCE (RFC 7636)
16
+ #
7
17
  # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
8
18
  class AuthCode < Base
9
19
  # The required query parameters for the authorize URL
10
20
  #
11
21
  # @param [Hash] params additional query parameters
12
22
  def authorize_params(params = {})
13
- params.merge('response_type' => 'code', 'client_id' => @client.id)
23
+ params.merge("response_type" => "code", "client_id" => @client.id)
14
24
  end
15
25
 
16
26
  # The authorization URL endpoint of the provider
@@ -28,7 +38,7 @@ module OAuth2
28
38
  # @param [Hash] opts access_token_opts, @see Client#get_token
29
39
  # @note that you must also provide a :redirect_uri with most OAuth 2.0 providers
30
40
  def get_token(code, params = {}, opts = {})
31
- params = {'grant_type' => 'authorization_code', 'code' => code}.merge(@client.redirection_params).merge(params)
41
+ params = {"grant_type" => "authorization_code", "code" => code}.merge(@client.redirection_params).merge(params)
32
42
  params_dup = params.dup
33
43
  params.each_key do |key|
34
44
  params_dup[key.to_s] = params_dup.delete(key) if key.is_a?(Symbol)
@@ -40,7 +50,7 @@ module OAuth2
40
50
  private
41
51
 
42
52
  def assert_valid_params(params)
43
- raise(ArgumentError, 'client_secret is not allowed in authorize URL query params') if params.key?(:client_secret) || params.key?('client_secret')
53
+ raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret")
44
54
  end
45
55
  end
46
56
  end
@@ -10,7 +10,7 @@ module OAuth2
10
10
  #
11
11
  # @raise [NotImplementedError]
12
12
  def authorize_url
13
- raise(NotImplementedError, 'The authorization endpoint is not used in this strategy')
13
+ raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
14
14
  end
15
15
 
16
16
  # Retrieve an access token given the specified client.
@@ -18,7 +18,7 @@ module OAuth2
18
18
  # @param [Hash] params additional params
19
19
  # @param [Hash] opts options
20
20
  def get_token(params = {}, opts = {})
21
- params = params.merge('grant_type' => 'client_credentials')
21
+ params = params.merge("grant_type" => "client_credentials")
22
22
  @client.get_token(params, opts)
23
23
  end
24
24
  end
@@ -4,13 +4,21 @@ module OAuth2
4
4
  module Strategy
5
5
  # The Implicit Strategy
6
6
  #
7
+ # IMPORTANT (OAuth 2.1): The Implicit grant (response_type=token) is omitted from the OAuth 2.1 draft specification.
8
+ # It remains here for backward compatibility with OAuth 2.0 providers. Prefer the Authorization Code flow with PKCE.
9
+ #
10
+ # References:
11
+ # - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
12
+ # - Why drop implicit: https://aaronparecki.com/2019/12/12/21/its-time-for-oauth-2-dot-1
13
+ # - Background: https://fusionauth.io/learn/expert-advice/oauth/differences-between-oauth-2-oauth-2-1/
14
+ #
7
15
  # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-26#section-4.2
8
16
  class Implicit < Base
9
17
  # The required query parameters for the authorize URL
10
18
  #
11
19
  # @param [Hash] params additional query parameters
12
20
  def authorize_params(params = {})
13
- params.merge('response_type' => 'token', 'client_id' => @client.id)
21
+ params.merge("response_type" => "token", "client_id" => @client.id)
14
22
  end
15
23
 
16
24
  # The authorization URL endpoint of the provider
@@ -25,13 +33,13 @@ module OAuth2
25
33
  #
26
34
  # @raise [NotImplementedError]
27
35
  def get_token(*)
28
- raise(NotImplementedError, 'The token is accessed differently in this strategy')
36
+ raise(NotImplementedError, "The token is accessed differently in this strategy")
29
37
  end
30
38
 
31
39
  private
32
40
 
33
41
  def assert_valid_params(params)
34
- raise(ArgumentError, 'client_secret is not allowed in authorize URL query params') if params.key?(:client_secret) || params.key?('client_secret')
42
+ raise(ArgumentError, "client_secret is not allowed in authorize URL query params") if params.key?(:client_secret) || params.key?("client_secret")
35
43
  end
36
44
  end
37
45
  end
@@ -4,13 +4,21 @@ module OAuth2
4
4
  module Strategy
5
5
  # The Resource Owner Password Credentials Authorization Strategy
6
6
  #
7
+ # IMPORTANT (OAuth 2.1): The Resource Owner Password Credentials grant is omitted in OAuth 2.1.
8
+ # It remains here for backward compatibility with OAuth 2.0 providers. Prefer Authorization Code + PKCE.
9
+ #
10
+ # References:
11
+ # - OAuth 2.1 draft: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
12
+ # - Okta explainer: https://developer.okta.com/blog/2019/12/13/oauth-2-1-how-many-rfcs
13
+ # - FusionAuth blog: https://fusionauth.io/blog/2020/04/15/whats-new-in-oauth-2-1
14
+ #
7
15
  # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.3
8
16
  class Password < Base
9
17
  # Not used for this strategy
10
18
  #
11
19
  # @raise [NotImplementedError]
12
20
  def authorize_url
13
- raise(NotImplementedError, 'The authorization endpoint is not used in this strategy')
21
+ raise(NotImplementedError, "The authorization endpoint is not used in this strategy")
14
22
  end
15
23
 
16
24
  # Retrieve an access token given the specified End User username and password.
@@ -19,9 +27,11 @@ module OAuth2
19
27
  # @param [String] password the End User password
20
28
  # @param [Hash] params additional params
21
29
  def get_token(username, password, params = {}, opts = {})
22
- params = {'grant_type' => 'password',
23
- 'username' => username,
24
- 'password' => password}.merge(params)
30
+ params = {
31
+ "grant_type" => "password",
32
+ "username" => username,
33
+ "password" => password,
34
+ }.merge(params)
25
35
  @client.get_token(params, opts)
26
36
  end
27
37
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module OAuth2
4
4
  module Version
5
- VERSION = '2.0.9'.freeze
5
+ VERSION = "2.0.22"
6
6
  end
7
+ VERSION = Version::VERSION # Traditional Constant Location
7
8
  end
data/lib/oauth2.rb CHANGED
@@ -1,40 +1,103 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # includes modules from stdlib
4
- require 'cgi'
5
- require 'time'
4
+ require "cgi/escape"
5
+ require "time"
6
6
 
7
7
  # third party gems
8
- require 'snaky_hash'
9
- require 'version_gem'
8
+ require "snaky_hash"
9
+ require "version_gem"
10
10
 
11
11
  # includes gem files
12
- require 'oauth2/version'
13
- require 'oauth2/error'
14
- require 'oauth2/authenticator'
15
- require 'oauth2/client'
16
- require 'oauth2/strategy/base'
17
- require 'oauth2/strategy/auth_code'
18
- require 'oauth2/strategy/implicit'
19
- require 'oauth2/strategy/password'
20
- require 'oauth2/strategy/client_credentials'
21
- require 'oauth2/strategy/assertion'
22
- require 'oauth2/access_token'
23
- require 'oauth2/response'
12
+ require_relative "oauth2/version"
13
+ require_relative "oauth2/auth_sanitizer"
14
+ require_relative "oauth2/filtered_attributes"
15
+ require_relative "oauth2/error"
16
+ require_relative "oauth2/authenticator"
17
+ require_relative "oauth2/client"
18
+ require_relative "oauth2/strategy/base"
19
+ require_relative "oauth2/strategy/auth_code"
20
+ require_relative "oauth2/strategy/implicit"
21
+ require_relative "oauth2/strategy/password"
22
+ require_relative "oauth2/strategy/client_credentials"
23
+ require_relative "oauth2/strategy/assertion"
24
+ require_relative "oauth2/access_token"
25
+ require_relative "oauth2/response"
24
26
 
25
27
  # The namespace of this library
28
+ #
29
+ # This module is the entry point and top-level namespace for the oauth2 gem.
30
+ # It exposes configuration, constants, and requires the primary public classes.
26
31
  module OAuth2
27
- DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(silence_extra_tokens_warning: false)
28
- @config = DEFAULT_CONFIG.dup
32
+ # When true, enables verbose HTTP logging via Faraday's logger middleware.
33
+ # Controlled by the OAUTH_DEBUG environment variable. Any case-insensitive
34
+ # value equal to "true" will enable debugging.
35
+ #
36
+ # @return [Boolean]
37
+ OAUTH_DEBUG = ENV.fetch("OAUTH_DEBUG", "false").casecmp("true").zero?
38
+
39
+ # Default configuration values for the oauth2 library.
40
+ #
41
+ # @example Toggle warnings
42
+ # OAuth2.configure do |config|
43
+ # config[:silence_extra_tokens_warning] = false
44
+ # config[:silence_no_tokens_warning] = false
45
+ # end
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
+ #
57
+ # @return [SnakyHash::SymbolKeyed] A mutable Hash-like config with symbol keys
58
+ DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(
59
+ silence_extra_tokens_warning: true,
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
+ ]
71
+ )
72
+
73
+ # The current runtime configuration for the library.
74
+ #
75
+ # @return [SnakyHash::SymbolKeyed]
76
+ CONFIG = DEFAULT_CONFIG.dup
77
+
29
78
  class << self
30
- attr_accessor :config
31
- end
32
- def configure
33
- yield @config
79
+ def config
80
+ CONFIG
81
+ end
82
+
83
+ # Configure global library behavior.
84
+ #
85
+ # Yields the mutable configuration object so callers can update settings.
86
+ #
87
+ # @yieldparam [SnakyHash::SymbolKeyed] config the configuration object
88
+ # @return [void]
89
+ def configure
90
+ yield config
91
+ end
34
92
  end
35
- module_function :configure
36
93
  end
37
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
+
100
+ # Extend OAuth2::Version with VersionGem helpers to provide semantic version helpers.
38
101
  OAuth2::Version.class_eval do
39
102
  extend VersionGem::Basic
40
103
  end