oauth2 1.4.11 → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/oauth2/client.rb CHANGED
@@ -22,15 +22,15 @@ module OAuth2
22
22
  # @param [Hash] options the options to create the client with
23
23
  # @option options [String] :site the OAuth2 provider site host
24
24
  # @option options [String] :redirect_uri the absolute URI to the Redirection Endpoint for use in authorization grants and token exchange
25
- # @option options [String] :authorize_url ('oauth/authorize') absolute or relative URL path to the Authorization endpoint
26
- # @option options [String] :token_url ('oauth/token') absolute or relative URL path to the Token endpoint
27
- # @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post)
25
+ # @option options [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
26
+ # @option options [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
27
+ # @option options [Symbol] :token_method (:post) HTTP method to use to request token (:get, :post, :post_with_query_string)
28
28
  # @option options [Symbol] :auth_scheme (:basic_auth) HTTP method to use to authorize request (:basic_auth or :request_body)
29
29
  # @option options [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
30
30
  # @option options [FixNum] :max_redirects (5) maximum number of redirects to follow
31
31
  # @option options [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error on responses with 400+ status codes
32
32
  # @option options [Logger] :logger (::Logger.new($stdout)) which logger to use when OAUTH_DEBUG is enabled
33
- # @option options [Proc] (DEPRECATED) :extract_access_token proc that extracts the access token from the response
33
+ # @option options [Proc] :extract_access_token proc that takes the client and the response Hash and extracts the access token from the response (DEPRECATED)
34
34
  # @yield [builder] The Faraday connection builder
35
35
  def initialize(client_id, client_secret, options = {}, &block)
36
36
  opts = options.dup
@@ -38,16 +38,18 @@ module OAuth2
38
38
  @secret = client_secret
39
39
  @site = opts.delete(:site)
40
40
  ssl = opts.delete(:ssl)
41
- @options = {:authorize_url => 'oauth/authorize',
42
- :token_url => 'oauth/token',
43
- :token_method => :post,
44
- :auth_scheme => :request_body,
45
- :connection_opts => {},
46
- :connection_build => block,
47
- :max_redirects => 5,
48
- :raise_errors => true,
49
- :extract_access_token => DEFAULT_EXTRACT_ACCESS_TOKEN, # DEPRECATED
50
- :logger => ::Logger.new($stdout)}.merge(opts)
41
+
42
+ @options = {
43
+ authorize_url: 'oauth/authorize',
44
+ token_url: 'oauth/token',
45
+ token_method: :post,
46
+ auth_scheme: :basic_auth,
47
+ connection_opts: {},
48
+ connection_build: block,
49
+ max_redirects: 5,
50
+ raise_errors: true,
51
+ logger: ::Logger.new($stdout),
52
+ }.merge(opts)
51
53
  @options[:connection_opts][:ssl] = ssl if ssl
52
54
  end
53
55
 
@@ -89,6 +91,9 @@ module OAuth2
89
91
  end
90
92
 
91
93
  # Makes a request relative to the specified site root.
94
+ # Updated HTTP 1.1 specification (IETF RFC 7231) relaxed the original constraint (IETF RFC 2616),
95
+ # allowing the use of relative URLs in Location headers.
96
+ # @see https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2
92
97
  #
93
98
  # @param [Symbol] verb one of :get, :post, :put, :delete
94
99
  # @param [String] url URL path of request
@@ -100,7 +105,7 @@ module OAuth2
100
105
  # code response for this request. Will default to client option
101
106
  # @option opts [Symbol] :parse @see Response::initialize
102
107
  # @yield [req] The Faraday request
103
- def request(verb, url, opts = {}) # rubocop:disable Metrics/AbcSize
108
+ def request(verb, url, opts = {})
104
109
  url = connection.build_url(url).to_s
105
110
 
106
111
  begin
@@ -112,7 +117,7 @@ module OAuth2
112
117
  raise ConnectionError, e
113
118
  end
114
119
 
115
- response = Response.new(response, :parse => opts[:parse])
120
+ response = Response.new(response, parse: opts[:parse])
116
121
 
117
122
  case response.status
118
123
  when 301, 302, 303, 307
@@ -126,7 +131,8 @@ module OAuth2
126
131
  end
127
132
  location = response.headers['location']
128
133
  if location
129
- request(verb, location, opts)
134
+ full_location = response.response.env.url.merge(location)
135
+ request(verb, full_location, opts)
130
136
  else
131
137
  error = Error.new(response)
132
138
  raise(error, "Got #{response.status} status code, but no Location header was present")
@@ -138,7 +144,6 @@ module OAuth2
138
144
  error = Error.new(response)
139
145
  raise(error) if opts.fetch(:raise_errors, options[:raise_errors])
140
146
 
141
- response.error = error
142
147
  response
143
148
  else
144
149
  error = Error.new(response)
@@ -150,20 +155,20 @@ module OAuth2
150
155
  #
151
156
  # @param params [Hash] a Hash of params for the token endpoint
152
157
  # @param access_token_opts [Hash] access token options, to pass to the AccessToken object
153
- # @param access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken
158
+ # @param extract_access_token [Proc] proc that extracts the access token from the response (DEPRECATED)
159
+ # @param access_token_class [Class] class of access token for easier subclassing OAuth2::AccessToken, @version 2.0+
154
160
  # @return [AccessToken] the initialized AccessToken
155
- def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token]) # # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity Metrics/AbcSize, Metrics/MethodLength
161
+ def get_token(params, access_token_opts = {}, extract_access_token = options[:extract_access_token], access_token_class: AccessToken)
156
162
  params = params.map do |key, value|
157
163
  if RESERVED_PARAM_KEYS.include?(key)
158
164
  [key.to_sym, value]
159
165
  else
160
166
  [key, value]
161
167
  end
162
- end
163
- params = Hash[params]
168
+ end.to_h
164
169
 
165
170
  params = authenticator.apply(params)
166
- opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
171
+ opts = {raise_errors: options[:raise_errors], parse: params.delete(:parse)}
167
172
  headers = params.delete(:headers) || {}
168
173
  if options[:token_method] == :post
169
174
  opts[:body] = params
@@ -172,29 +177,19 @@ module OAuth2
172
177
  opts[:params] = params
173
178
  opts[:headers] = {}
174
179
  end
175
- opts[:headers] = opts[:headers].merge(headers)
180
+ opts[:headers].merge!(headers)
176
181
  http_method = options[:token_method]
182
+ http_method = :post if http_method == :post_with_query_string
177
183
  response = request(http_method, token_url, opts)
178
184
 
179
- access_token = begin
180
- build_access_token(response, access_token_opts, extract_access_token)
181
- rescue StandardError
182
- nil
183
- end
184
-
185
- response_contains_token = access_token || (
186
- response.parsed.is_a?(Hash) &&
187
- (response.parsed['access_token'] || response.parsed['id_token'])
188
- )
189
-
190
- if options[:raise_errors] && !response_contains_token
191
- error = Error.new(response)
192
- raise(error)
193
- elsif !response_contains_token
194
- return nil
185
+ # In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
186
+ # We preserve this behavior here, but a custom access_token_class that implements #from_hash
187
+ # should be used instead.
188
+ if extract_access_token
189
+ parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
190
+ else
191
+ parse_response(response, access_token_opts, access_token_class)
195
192
  end
196
-
197
- access_token
198
193
  end
199
194
 
200
195
  # The Authorization Code strategy
@@ -253,11 +248,6 @@ module OAuth2
253
248
  end
254
249
  end
255
250
 
256
- DEFAULT_EXTRACT_ACCESS_TOKEN = proc do |client, hash|
257
- token = hash.delete('access_token') || hash.delete(:access_token)
258
- token && AccessToken.new(client, token, hash)
259
- end
260
-
261
251
  private
262
252
 
263
253
  # Returns the authenticator object
@@ -267,26 +257,50 @@ module OAuth2
267
257
  Authenticator.new(id, secret, options[:auth_scheme])
268
258
  end
269
259
 
260
+ def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
261
+ access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
262
+
263
+ return access_token if access_token
264
+
265
+ if options[:raise_errors]
266
+ error = Error.new(response)
267
+ raise(error)
268
+ end
269
+
270
+ nil
271
+ end
272
+
273
+ def parse_response(response, access_token_opts, access_token_class)
274
+ data = response.parsed
275
+
276
+ if options[:raise_errors] && data.is_a?(Hash) && !access_token_class.contains_token?(data)
277
+ error = Error.new(response)
278
+ raise(error)
279
+ end
280
+
281
+ build_access_token(response, access_token_opts, access_token_class)
282
+ end
283
+
270
284
  # Builds the access token from the response of the HTTP call
271
285
  #
272
286
  # @return [AccessToken] the initialized AccessToken
273
- def build_access_token(response, access_token_opts, extract_access_token)
274
- parsed_response = response.parsed.dup
275
- return unless parsed_response.is_a?(Hash)
276
-
277
- hash = parsed_response.merge(access_token_opts)
278
-
279
- # Provide backwards compatibility for old AccessToken.form_hash pattern
280
- # Will be deprecated in 2.x
281
- if extract_access_token.is_a?(Class) && extract_access_token.respond_to?(:from_hash)
282
- extract_access_token.from_hash(self, hash)
283
- else
284
- extract_access_token.call(self, hash)
287
+ def build_access_token(response, access_token_opts, access_token_class)
288
+ access_token_class.from_hash(self, response.parsed.merge(access_token_opts)).tap do |access_token|
289
+ access_token.response = response if access_token.respond_to?(:response=)
285
290
  end
286
291
  end
287
292
 
293
+ # Builds the access token from the response of the HTTP call with legacy extract_access_token
294
+ #
295
+ # @return [AccessToken] the initialized AccessToken
296
+ def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
297
+ extract_access_token.call(self, response.parsed.merge(access_token_opts))
298
+ rescue StandardError
299
+ nil
300
+ end
301
+
288
302
  def oauth_debug_logging(builder)
289
- builder.response :logger, options[:logger], :bodies => true if ENV['OAUTH_DEBUG'] == 'true'
303
+ builder.response :logger, options[:logger], bodies: true if ENV['OAUTH_DEBUG'] == 'true'
290
304
  end
291
305
  end
292
306
  end
data/lib/oauth2/error.rb CHANGED
@@ -4,39 +4,48 @@ module OAuth2
4
4
  class Error < StandardError
5
5
  attr_reader :response, :code, :description
6
6
 
7
- # standard error values include:
8
- # :invalid_request, :invalid_client, :invalid_token, :invalid_grant, :unsupported_grant_type, :invalid_scope
7
+ # standard error codes include:
8
+ # 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
9
9
  def initialize(response)
10
- response.error = self
11
10
  @response = response
11
+ message_opts = {}
12
12
 
13
13
  if response.parsed.is_a?(Hash)
14
14
  @code = response.parsed['error']
15
15
  @description = response.parsed['error_description']
16
- error_description = "#{@code}: #{@description}"
16
+ message_opts = parse_error_description(@code, @description)
17
17
  end
18
18
 
19
- super(error_message(response.body, :error_description => error_description))
19
+ super(error_message(response.body, message_opts))
20
20
  end
21
21
 
22
- # Makes a error message
23
- # @param [String] response_body response body of request
24
- # @param [String] opts :error_description error description to show first line
22
+ private
23
+
25
24
  def error_message(response_body, opts = {})
26
- message = []
25
+ lines = []
26
+
27
+ lines << opts[:error_description] if opts[:error_description]
28
+
29
+ error_string = if response_body.respond_to?(:encode) && opts[:error_description].respond_to?(:encoding)
30
+ script_encoding = opts[:error_description].encoding
31
+ response_body.encode(script_encoding, invalid: :replace, undef: :replace)
32
+ else
33
+ response_body
34
+ end
27
35
 
28
- opts[:error_description] && (message << opts[:error_description])
36
+ lines << error_string
37
+
38
+ lines.join("\n")
39
+ end
29
40
 
30
- error_message = if opts[:error_description] && opts[:error_description].respond_to?(:encoding)
31
- script_encoding = opts[:error_description].encoding
32
- response_body.encode(script_encoding, :invalid => :replace, :undef => :replace)
33
- else
34
- response_body
35
- end
41
+ def parse_error_description(code, description)
42
+ return {} unless code || description
36
43
 
37
- message << error_message
44
+ error_description = ''
45
+ error_description += "#{code}: " if code
46
+ error_description += description if description
38
47
 
39
- message.join("\n")
48
+ {error_description: error_description}
40
49
  end
41
50
  end
42
51
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'multi_json'
3
+ require 'json'
4
4
  require 'multi_xml'
5
5
  require 'rack'
6
6
 
@@ -8,20 +8,17 @@ module OAuth2
8
8
  # OAuth2::Response class
9
9
  class Response
10
10
  attr_reader :response
11
- attr_accessor :error, :options
11
+ attr_accessor :options
12
12
 
13
13
  # Procs that, when called, will parse a response body according
14
14
  # to the specified format.
15
15
  @@parsers = {
16
- :json => lambda { |body| MultiJson.load(body) rescue body }, # rubocop:disable Style/RescueModifier
17
- :query => lambda { |body| Rack::Utils.parse_query(body) },
18
- :text => lambda { |body| body },
16
+ query: ->(body) { Rack::Utils.parse_query(body) },
17
+ text: ->(body) { body },
19
18
  }
20
19
 
21
20
  # Content type assignments for various potential HTTP content types.
22
21
  @@content_types = {
23
- 'application/json' => :json,
24
- 'text/javascript' => :json,
25
22
  'application/x-www-form-urlencoded' => :query,
26
23
  'text/plain' => :text,
27
24
  }
@@ -47,7 +44,7 @@ module OAuth2
47
44
  # :json, or :automatic (determined by Content-Type response header)
48
45
  def initialize(response, opts = {})
49
46
  @response = response
50
- @options = {:parse => :automatic}.merge(opts)
47
+ @options = {parse: :automatic}.merge(opts)
51
48
  end
52
49
 
53
50
  # The HTTP response headers
@@ -65,29 +62,74 @@ module OAuth2
65
62
  response.body || ''
66
63
  end
67
64
 
68
- # The parsed response body.
69
- # Will attempt to parse application/x-www-form-urlencoded and
70
- # application/json Content-Type response bodies
65
+ # The {#response} {#body} as parsed by {#parser}.
66
+ #
67
+ # @return [Object] As returned by {#parser} if it is #call-able.
68
+ # @return [nil] If the {#parser} is not #call-able.
71
69
  def parsed
72
- return nil unless @@parsers.key?(parser)
70
+ return @parsed if defined?(@parsed)
71
+
72
+ @parsed =
73
+ if parser.respond_to?(:call)
74
+ case parser.arity
75
+ when 0
76
+ parser.call
77
+ when 1
78
+ parser.call(body)
79
+ else
80
+ parser.call(body, response)
81
+ end
82
+ end
83
+
84
+ @parsed = OAuth2::SnakyHash.new(@parsed) if @parsed.is_a?(Hash)
73
85
 
74
- @parsed ||= @@parsers[parser].call(body)
86
+ @parsed
75
87
  end
76
88
 
77
89
  # Attempts to determine the content type of the response.
78
90
  def content_type
79
- ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip
91
+ return nil unless response.headers
92
+
93
+ ((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip.downcase
80
94
  end
81
95
 
82
- # Determines the parser that will be used to supply the content of #parsed
96
+ # Determines the parser (a Proc or other Object which responds to #call)
97
+ # that will be passed the {#body} (and optional {#response}) to supply
98
+ # {#parsed}.
99
+ #
100
+ # The parser can be supplied as the +:parse+ option in the form of a Proc
101
+ # (or other Object responding to #call) or a Symbol. In the latter case,
102
+ # the actual parser will be looked up in {@@parsers} by the supplied Symbol.
103
+ #
104
+ # If no +:parse+ option is supplied, the lookup Symbol will be determined
105
+ # by looking up {#content_type} in {@@content_types}.
106
+ #
107
+ # If {#parser} is a Proc, it will be called with no arguments, just
108
+ # {#body}, or {#body} and {#response}, depending on the Proc's arity.
109
+ #
110
+ # @return [Proc, #call] If a parser was found.
111
+ # @return [nil] If no parser was found.
83
112
  def parser
84
- return options[:parse].to_sym if @@parsers.key?(options[:parse])
113
+ return @parser if defined?(@parser)
114
+
115
+ @parser =
116
+ if options[:parse].respond_to?(:call)
117
+ options[:parse]
118
+ elsif options[:parse]
119
+ @@parsers[options[:parse].to_sym]
120
+ end
85
121
 
86
- @@content_types[content_type]
122
+ @parser ||= @@parsers[@@content_types[content_type]]
87
123
  end
88
124
  end
89
125
  end
90
126
 
91
- OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml']) do |body|
92
- MultiXml.parse(body) rescue body # rubocop:disable Style/RescueModifier
127
+ OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml', 'application/xml']) do |body|
128
+ MultiXml.parse(body)
129
+ end
130
+
131
+ 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|
132
+ body = body.dup.force_encoding(::Encoding::ASCII_8BIT) if body.respond_to?(:force_encoding)
133
+
134
+ ::JSON.parse(body)
93
135
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OAuth2
4
+ # Hash which allow assign string key in camel case
5
+ # and query on both camel and snake case
6
+ class SnakyHash < ::Hashie::Mash::Rash
7
+ end
8
+ end
@@ -10,15 +10,22 @@ module OAuth2
10
10
  #
11
11
  # Sample usage:
12
12
  # client = OAuth2::Client.new(client_id, client_secret,
13
- # :site => 'http://localhost:8080')
13
+ # :site => 'http://localhost:8080',
14
+ # :auth_scheme => :request_body)
14
15
  #
15
- # params = {:hmac_secret => "some secret",
16
- # # or :private_key => "private key string",
17
- # :iss => "http://localhost:3001",
18
- # :prn => "me@here.com",
19
- # :exp => Time.now.utc.to_i + 3600}
16
+ # claim_set = {
17
+ # :iss => "http://localhost:3001",
18
+ # :aud => "http://localhost:8080/oauth2/token"
19
+ # :sub => "me@example.com",
20
+ # :exp => Time.now.utc.to_i + 3600,
21
+ # }
20
22
  #
21
- # access = client.assertion.get_token(params)
23
+ # encoding = {
24
+ # :algorithm => 'HS256',
25
+ # :key => 'secret_key',
26
+ # }
27
+ #
28
+ # access = client.assertion.get_token(claim_set, encoding)
22
29
  # access.token # actual access_token string
23
30
  # access.get("/api/stuff") # making api calls with access token in header
24
31
  #
@@ -32,45 +39,63 @@ module OAuth2
32
39
 
33
40
  # Retrieve an access token given the specified client.
34
41
  #
35
- # @param [Hash] params assertion params
36
- # pass either :hmac_secret or :private_key, but not both.
42
+ # @param [Hash] claims the hash representation of the claims that should be encoded as a JWT (JSON Web Token)
43
+ #
44
+ # For reading on JWT and claim keys:
45
+ # @see https://github.com/jwt/ruby-jwt
46
+ # @see https://datatracker.ietf.org/doc/html/rfc7519#section-4.1
47
+ # @see https://datatracker.ietf.org/doc/html/rfc7523#section-3
48
+ # @see https://www.iana.org/assignments/jwt/jwt.xhtml
49
+ #
50
+ # There are many possible claim keys, and applications may ask for their own custom keys.
51
+ # Some typically required ones:
52
+ # :iss (issuer)
53
+ # :aud (audience)
54
+ # :sub (subject) -- formerly :prn https://datatracker.ietf.org/doc/html/draft-ietf-oauth-json-web-token-06#appendix-F
55
+ # :exp, (expiration time) -- in seconds, e.g. Time.now.utc.to_i + 3600
56
+ #
57
+ # Note that this method does *not* validate presence of those four claim keys indicated as required by RFC 7523.
58
+ # There are endpoints that may not conform with this RFC, and this gem should still work for those use cases.
59
+ #
60
+ # @param [Hash] encoding_opts a hash containing instructions on how the JWT should be encoded
61
+ # @option algorithm [String] the algorithm with which you would like the JWT to be encoded
62
+ # @option key [Object] the key with which you would like to encode the JWT
37
63
  #
38
- # params :hmac_secret, secret string.
39
- # params :private_key, private key string.
64
+ # These two options are passed directly to `JWT.encode`. For supported encoding arguments:
65
+ # @see https://github.com/jwt/ruby-jwt#algorithms-and-usage
66
+ # @see https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
40
67
  #
41
- # params :iss, issuer
42
- # params :aud, audience, optional
43
- # params :prn, principal, current user
44
- # params :exp, expired at, in seconds, like Time.now.utc.to_i + 3600
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')})
45
71
  #
46
- # @param [Hash] opts options
47
- def get_token(params = {}, opts = {})
48
- hash = build_request(params)
49
- @client.get_token(hash, opts.merge('refresh_token' => nil))
72
+ # @param [Hash] request_opts options that will be used to assemble the request
73
+ # @option request_opts [String] :scope the url parameter `scope` that may be required by some endpoints
74
+ # @see https://datatracker.ietf.org/doc/html/rfc7521#section-4.1
75
+ #
76
+ # @param [Hash] response_opts this will be merged with the token response to create the AccessToken object
77
+ # @see the access_token_opts argument to Client#get_token
78
+
79
+ def get_token(claims, encoding_opts, request_opts = {}, response_opts = {})
80
+ assertion = build_assertion(claims, encoding_opts)
81
+ params = build_request(assertion, request_opts)
82
+
83
+ @client.get_token(params, response_opts.merge('refresh_token' => nil))
50
84
  end
51
85
 
52
- def build_request(params)
53
- assertion = build_assertion(params)
86
+ private
87
+
88
+ def build_request(assertion, request_opts = {})
54
89
  {
55
- :grant_type => 'assertion',
56
- :assertion_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
57
- :assertion => assertion,
58
- :scope => params[:scope],
59
- }
90
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
91
+ assertion: assertion,
92
+ }.merge(request_opts)
60
93
  end
61
94
 
62
- def build_assertion(params)
63
- claims = {
64
- :iss => params[:iss],
65
- :aud => params[:aud],
66
- :prn => params[:prn],
67
- :exp => params[:exp],
68
- }
69
- if params[:hmac_secret]
70
- JWT.encode(claims, params[:hmac_secret], 'HS256')
71
- elsif params[:private_key]
72
- JWT.encode(claims, params[:private_key], 'RS256')
73
- end
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?
97
+
98
+ JWT.encode(claims, encoding_opts[:key], encoding_opts[:algorithm])
74
99
  end
75
100
  end
76
101
  end
@@ -17,6 +17,7 @@ module OAuth2
17
17
  #
18
18
  # @param [Hash] params additional query parameters for the URL
19
19
  def authorize_url(params = {})
20
+ assert_valid_params(params)
20
21
  @client.authorize_url(authorize_params.merge(params))
21
22
  end
22
23
 
@@ -28,8 +29,18 @@ module OAuth2
28
29
  # @note that you must also provide a :redirect_uri with most OAuth 2.0 providers
29
30
  def get_token(code, params = {}, opts = {})
30
31
  params = {'grant_type' => 'authorization_code', 'code' => code}.merge(@client.redirection_params).merge(params)
32
+ params_dup = params.dup
33
+ params.each_key do |key|
34
+ params_dup[key.to_s] = params_dup.delete(key) if key.is_a?(Symbol)
35
+ end
31
36
 
32
- @client.get_token(params, opts)
37
+ @client.get_token(params_dup, opts)
38
+ end
39
+
40
+ private
41
+
42
+ 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')
33
44
  end
34
45
  end
35
46
  end
@@ -17,6 +17,7 @@ module OAuth2
17
17
  #
18
18
  # @param [Hash] params additional query parameters for the URL
19
19
  def authorize_url(params = {})
20
+ assert_valid_params(params)
20
21
  @client.authorize_url(authorize_params.merge(params))
21
22
  end
22
23
 
@@ -26,6 +27,12 @@ module OAuth2
26
27
  def get_token(*)
27
28
  raise(NotImplementedError, 'The token is accessed differently in this strategy')
28
29
  end
30
+
31
+ private
32
+
33
+ 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')
35
+ end
29
36
  end
30
37
  end
31
38
  end