oauth2 2.0.9 → 2.0.25

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,77 @@
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
- if response.parsed.is_a?(Hash)
14
- @code = response.parsed['error']
15
- @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"]
16
27
  end
17
28
  elsif response.is_a?(Hash)
18
- @code = response['error']
19
- @description = response['error_description']
29
+ @code = response["error"]
30
+ @description = response["error_description"]
20
31
  end
21
32
  @body = if response.respond_to?(:body)
22
- response.body
23
- else
24
- @response
25
- end
33
+ response.body
34
+ else
35
+ @response
36
+ end
26
37
  message_opts = parse_error_description(@code, @description)
27
38
  super(error_message(@body, message_opts))
28
39
  end
29
40
 
30
41
  private
31
42
 
43
+ # Builds a multi-line error message including description and raw body.
44
+ #
45
+ # @param [String, #encode] response_body Response body content
46
+ # @param [Hash] opts Options including :error_description
47
+ # @return [String] Message suitable for StandardError
32
48
  def error_message(response_body, opts = {})
33
49
  lines = []
50
+ error_description = opts[:error_description]
34
51
 
35
- lines << opts[:error_description] if opts[:error_description]
52
+ lines << error_description if error_description
36
53
 
37
- 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
54
+ error_string = if response_body.respond_to?(:encode) && error_description.respond_to?(:encoding)
55
+ script_encoding = error_description.encoding
56
+ response_body.encode(script_encoding, invalid: :replace, undef: :replace)
57
+ else
58
+ response_body
59
+ end
43
60
 
44
61
  lines << error_string
45
62
 
46
63
  lines.join("\n")
47
64
  end
48
65
 
66
+ # Formats the OAuth2 error code and description into a single string.
67
+ #
68
+ # @param [String, nil] code OAuth2 error code
69
+ # @param [String, nil] description OAuth2 error description
70
+ # @return [Hash] Options hash containing :error_description when present
49
71
  def parse_error_description(code, description)
50
72
  return {} unless code || description
51
73
 
52
- error_description = ''
74
+ error_description = ""
53
75
  error_description += "#{code}: " if code
54
76
  error_description += description if description
55
77
 
@@ -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,131 +1,168 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
4
- require 'multi_xml'
5
- require 'rack'
3
+ require "json"
4
+ require "set"
5
+ require "multi_xml"
6
+ require "rack"
6
7
 
7
8
  module OAuth2
8
- # OAuth2::Response class
9
+ # The Response class handles HTTP responses in the OAuth2 gem, providing methods
10
+ # to access and parse response data in various formats.
11
+ #
12
+ # @since 1.0.0
9
13
  class Response
14
+ # Default configuration options for Response instances
15
+ #
16
+ # @return [Hash] The default options hash
10
17
  DEFAULT_OPTIONS = {
11
18
  parse: :automatic,
12
19
  snaky: true,
20
+ snaky_hash_klass: SnakyHash::StringKeyed,
13
21
  }.freeze
22
+
23
+ # @return [Faraday::Response] The raw Faraday response object
14
24
  attr_reader :response
25
+
26
+ # @return [Hash] The options hash for this instance
15
27
  attr_accessor :options
16
28
 
17
- # Procs that, when called, will parse a response body according
18
- # to the specified format.
29
+ # @private
30
+ # Storage for response body parser procedures
31
+ #
32
+ # @return [Hash<Symbol, Proc>] Hash of parser procs keyed by format symbol
19
33
  @@parsers = {
20
34
  query: ->(body) { Rack::Utils.parse_query(body) },
21
35
  text: ->(body) { body },
22
36
  }
23
37
 
24
- # Content type assignments for various potential HTTP content types.
38
+ # @private
39
+ # Maps content types to parser symbols
40
+ #
41
+ # @return [Hash<String, Symbol>] Hash of content types mapped to parser symbols
25
42
  @@content_types = {
26
- 'application/x-www-form-urlencoded' => :query,
27
- 'text/plain' => :text,
43
+ "application/x-www-form-urlencoded" => :query,
44
+ "text/plain" => :text,
28
45
  }
29
46
 
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
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
40
61
  end
41
62
  end
42
63
 
43
64
  # Initializes a Response instance
44
65
  #
45
66
  # @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)
67
+ # @param [Symbol] parse (:automatic) How to parse the response body
68
+ # @param [Boolean] snaky (true) Whether to convert parsed response to snake_case using SnakyHash
69
+ # @param [Class, nil] snaky_hash_klass (nil) Custom class for snake_case hash conversion
70
+ # @param [Hash] options Additional options for the response
71
+ # @option options [Symbol] :parse (:automatic) Parse strategy (:query, :json, or :automatic)
72
+ # @option options [Boolean] :snaky (true) Enable/disable snake_case conversion
73
+ # @option options [Class] :snaky_hash_klass (SnakyHash::StringKeyed) Class to use for hash conversion
74
+ # @return [OAuth2::Response] The new Response instance
75
+ def initialize(response, parse: :automatic, snaky: true, snaky_hash_klass: nil, **options)
52
76
  @response = response
53
77
  @options = {
54
78
  parse: parse,
55
79
  snaky: snaky,
80
+ snaky_hash_klass: snaky_hash_klass,
56
81
  }.merge(options)
57
82
  end
58
83
 
59
84
  # The HTTP response headers
85
+ #
86
+ # @return [Hash] The response headers
60
87
  def headers
61
88
  response.headers
62
89
  end
63
90
 
64
91
  # The HTTP response status code
92
+ #
93
+ # @return [Integer] The response status code
65
94
  def status
66
95
  response.status
67
96
  end
68
97
 
69
98
  # The HTTP response body
99
+ #
100
+ # @return [String] The response body or empty string if nil
70
101
  def body
71
- response.body || ''
102
+ response.body || ""
72
103
  end
73
104
 
74
- # The {#response} {#body} as parsed by {#parser}.
105
+ # The parsed response body
75
106
  #
76
- # @return [Object] As returned by {#parser} if it is #call-able.
77
- # @return [nil] If the {#parser} is not #call-able.
107
+ # @return [Object, SnakyHash::StringKeyed] The parsed response body
108
+ # @return [nil] If no parser is available
78
109
  def parsed
79
110
  return @parsed if defined?(@parsed)
80
111
 
112
+ response_parser = parser
81
113
  @parsed =
82
- if parser.respond_to?(:call)
83
- case parser.arity
114
+ if response_parser.respond_to?(:call)
115
+ case response_parser.arity
84
116
  when 0
85
- parser.call
117
+ response_parser.call
86
118
  when 1
87
- parser.call(body)
119
+ response_parser.call(body)
88
120
  else
89
- parser.call(body, response)
121
+ response_parser.call(body, response)
90
122
  end
91
123
  end
92
124
 
93
- @parsed = SnakyHash::StringKeyed.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)
125
+ if options[:snaky] && @parsed.is_a?(Hash)
126
+ hash_klass = options[:snaky_hash_klass] || DEFAULT_OPTIONS[:snaky_hash_klass]
127
+ @parsed = hash_klass[@parsed]
128
+ end
94
129
 
95
130
  @parsed
96
131
  end
97
132
 
98
- # Attempts to determine the content type of the response.
133
+ # Determines the content type of the response
134
+ #
135
+ # @return [String, nil] The content type or nil if headers are not present
99
136
  def content_type
100
- return nil unless response.headers
137
+ response_headers = response.headers
138
+ return unless response_headers
101
139
 
102
- ((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
103
141
  end
104
142
 
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}.
143
+ # Determines the parser to be used for the response body
108
144
  #
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.
145
+ # @note The parser can be supplied as the +:parse+ option in the form of a Proc
146
+ # (or other Object responding to #call) or a Symbol. In the latter case,
147
+ # the actual parser will be looked up in {@@parsers} by the supplied Symbol.
112
148
  #
113
- # If no +:parse+ option is supplied, the lookup Symbol will be determined
114
- # by looking up {#content_type} in {@@content_types}.
149
+ # @note If no +:parse+ option is supplied, the lookup Symbol will be determined
150
+ # by looking up {#content_type} in {@@content_types}.
115
151
  #
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.
152
+ # @note If {#parser} is a Proc, it will be called with no arguments, just
153
+ # {#body}, or {#body} and {#response}, depending on the Proc's arity.
118
154
  #
119
- # @return [Proc, #call] If a parser was found.
120
- # @return [nil] If no parser was found.
155
+ # @return [Proc, #call] The parser proc or callable object
156
+ # @return [nil] If no suitable parser is found
121
157
  def parser
122
158
  return @parser if defined?(@parser)
123
159
 
160
+ parse_option = options[:parse]
124
161
  @parser =
125
- if options[:parse].respond_to?(:call)
126
- options[:parse]
127
- elsif options[:parse]
128
- @@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]
129
166
  end
130
167
 
131
168
  @parser ||= @@parsers[@@content_types[content_type]]
@@ -133,16 +170,21 @@ module OAuth2
133
170
  end
134
171
  end
135
172
 
136
- OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body|
173
+ # Register XML parser
174
+ # @api private
175
+ OAuth2::Response.register_parser(:xml, ["text/xml", "application/rss+xml", "application/rdf+xml", "application/atom+xml", "application/xml"]) do |body|
137
176
  next body unless body.respond_to?(:to_str)
138
177
 
139
- MultiXml.parse(body)
178
+ (defined?(MultiXML) ? MultiXML : MultiXml).parse(body)
140
179
  end
141
180
 
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|
181
+ # Register JSON parser
182
+ # @api private
183
+ 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
184
  next body unless body.respond_to?(:to_str)
144
185
 
145
- body = body.dup.force_encoding(::Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
186
+ body = body.dup.force_encoding(Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
187
+ next body if body.respond_to?(:empty?) && body.empty?
146
188
 
147
- ::JSON.parse(body)
189
+ JSON.parse(body)
148
190
  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.25"
6
6
  end
7
+ VERSION = Version::VERSION # Traditional Constant Location
7
8
  end