oauth2 2.0.5 → 2.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b016b4a0d35d5e6b17d60c9417f7a456b78a38462120fff7d68021235dee6f6d
4
- data.tar.gz: 5627dc50a7dfc395f226a1209606aa63d1c8c9642ba6aba390f5ba3605567b33
3
+ metadata.gz: 71b8f6f9abb6afbd1cdeffbdb50b84906b0f8b44e35f9db1ebbb8c6e7acd50ba
4
+ data.tar.gz: a1e958b150f5909cf05734724371df99121e24d1c6581b64fa83d6326e448a6d
5
5
  SHA512:
6
- metadata.gz: cbbfb987df74ec80833a13f2d7ae5fc090af533cfe3e0ce7146ed3f1dcec45159a8ac4447c0aacbc5ad2c9e8490d76a9c227dcb857b9fc2cc4a5b6b6634d1b41
7
- data.tar.gz: ebf819a7fcfb1c66041bb01b46f023fcfd8cb06f1762ff938795faab71e4871d00986899c592465e8424ebd40eff1e90c6a85a77e4717f4211a07ec41a148144
6
+ metadata.gz: 5d3f859ea2a0b1ab53de9fda075f44b23c6c0426ba9af339b7cccadd9a613c44b252182819cd0221bac2cd97a9a21e91873af32d4e9bdbe0c5414f0ab0b5563f
7
+ data.tar.gz: 1802ba5465d719b80fc99f0802d0e1531288e3e4449e08b76abf070de0e26ebbf92a027fe468bfe4ba768293a6cda5f4b7458cfcb6d2d59cf297e269691ed22c
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
4
4
  The format (since v2) is based on [Keep a Changelog v1](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [Unreleased]
8
+ ### Added
9
+ ### Changed
10
+ ### Fixed
11
+ ### Removed
12
+
13
+ ## [2.0.8] - 2022-09-01
14
+ ### Changed
15
+ - [!630](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/630) - Extract snaky_hash to external dependency (@pboling)
16
+
17
+ ### Added
18
+ - [!631](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/631) - New global configuration option OAuth2.config.silence_extra_tokens_warning (default: false) fixes [#628](https://gitlab.com/oauth-xx/oauth2/-/issues/628)
19
+
20
+ ## [2.0.7] - 2022-08-22
21
+ ### Added
22
+ - [#629](https://github.com/oauth-xx/oauth2/pull/629) - Allow POST of JSON to get token (@pboling, @terracatta)
23
+
24
+ ### Fixed
25
+ - [#626](https://github.com/oauth-xx/oauth2/pull/626) - Fixes a regression in 2.0.6. Will now prefer the key order from the lookup, not the hash keys (@rickselby)
26
+ - Note: This fixes compatibility with `omniauth-oauth2` and AWS
27
+ - [#625](https://github.com/oauth-xx/oauth2/pull/625) - Fixes the printed version in the post install message (@hasghari)
28
+
29
+ ## [2.0.6] - 2022-07-13
30
+ ### Fixed
31
+ - [#624](https://github.com/oauth-xx/oauth2/pull/624) - Fixes a [regression](https://github.com/oauth-xx/oauth2/pull/623) in v2.0.5, where an error would be raised in refresh_token flows due to (legitimate) lack of access_token (@pboling)
32
+
7
33
  ## [2.0.5] - 2022-07-07
8
34
  ### Fixed
9
35
  - [#620](https://github.com/oauth-xx/oauth2/pull/620) - Documentation improvements, to help with upgrading (@swanson)
@@ -292,5 +318,7 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
292
318
  [2.0.3]: https://github.com/oauth-xx/oauth2/compare/v2.0.2...v2.0.3
293
319
  [2.0.4]: https://github.com/oauth-xx/oauth2/compare/v2.0.3...v2.0.4
294
320
  [2.0.5]: https://github.com/oauth-xx/oauth2/compare/v2.0.4...v2.0.5
295
- [Unreleased]: https://github.com/oauth-xx/oauth2/compare/v2.0.5...HEAD
321
+ [2.0.6]: https://github.com/oauth-xx/oauth2/compare/v2.0.5...v2.0.6
322
+ [2.0.7]: https://github.com/oauth-xx/oauth2/compare/v2.0.6...v2.0.7
323
+ [Unreleased]: https://github.com/oauth-xx/oauth2/compare/v2.0.7...HEAD
296
324
  [gemfiles/readme]: gemfiles/README.md
data/README.md CHANGED
@@ -32,6 +32,9 @@ See the sibling `oauth` gem for OAuth 1.0 implementations in Ruby.
32
32
 
33
33
  | Version | Release Date | Readme |
34
34
  |---------|--------------|----------------------------------------------------------|
35
+ | 2.0.7 | 2022-08-22 | https://github.com/oauth-xx/oauth2/blob/v2.0.7/README.md |
36
+ | 2.0.6 | 2022-07-13 | https://github.com/oauth-xx/oauth2/blob/v2.0.6/README.md |
37
+ | 2.0.5 | 2022-07-07 | https://github.com/oauth-xx/oauth2/blob/v2.0.5/README.md |
35
38
  | 2.0.4 | 2022-07-01 | https://github.com/oauth-xx/oauth2/blob/v2.0.4/README.md |
36
39
  | 2.0.3 | 2022-06-28 | https://github.com/oauth-xx/oauth2/blob/v2.0.3/README.md |
37
40
  | 2.0.2 | 2022-06-24 | https://github.com/oauth-xx/oauth2/blob/v2.0.2/README.md |
@@ -143,8 +146,8 @@ The link tokens in the following sections should be kept ordered by the row and
143
146
  [🖐prs-o-img]: https://img.shields.io/github/issues-pr/oauth-xx/oauth2
144
147
  [🧮prs-c]: https://github.com/oauth-xx/oauth2/pulls?q=is%3Apr+is%3Aclosed
145
148
  [🧮prs-c-img]: https://img.shields.io/github/issues-pr-closed/oauth-xx/oauth2
146
- [📗next♻️]: https://github.com/oauth-xx/oauth2/milestone/15
147
- [📗next-img♻️]: https://img.shields.io/github/milestones/progress/oauth-xx/oauth2/15?label=Next%20Version
149
+ [📗next♻️]: https://github.com/oauth-xx/oauth2/milestone/2
150
+ [📗next-img♻️]: https://img.shields.io/github/milestones/progress/oauth-xx/oauth2/2?label=Next%20Version
148
151
 
149
152
  <!-- 3️⃣ maintenance & linting -->
150
153
  [⛳cclim-maint]: https://codeclimate.com/github/oauth-xx/oauth2/maintainability
data/SECURITY.md CHANGED
@@ -2,11 +2,15 @@
2
2
 
3
3
  ## Supported Versions
4
4
 
5
- | Version | Supported |
6
- |----------|---------------------------|
7
- | 2.latest | ✅ |
8
- | 1.latest | ✅ (security updates only) |
9
- | older | ⛔️ |
5
+ | Version | Supported | EOL | Post-EOL / Enterprise |
6
+ |----------|-----------|---------|---------------------------------------|
7
+ | 2.latest | ✅ | 04/2024 | [Tidelift Subscription][tidelift-ref] |
8
+ | 1.latest | ✅ | 04/2023 | [Tidelift Subscription][tidelift-ref] |
9
+ | <= 1 | | ⛔ | ⛔ |
10
+
11
+ ### EOL Policy
12
+
13
+ Non-commercial support for the oldest version of Ruby (which itself is going EOL) will be dropped each year in April.
10
14
 
11
15
  ## Reporting a Vulnerability
12
16
 
@@ -17,4 +21,6 @@ Tidelift will coordinate the fix and disclosure.
17
21
 
18
22
  Available as part of the Tidelift Subscription.
19
23
 
20
- The maintainers of oauth2 and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. [Learn more.](https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
24
+ The maintainers of oauth2 and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. [Learn more.][tidelift-ref]
25
+
26
+ [tidelift-ref]: https://tidelift.com/subscription/pkg/rubygems-oauth2?utm_source=rubygems-oauth2&utm_medium=referral&utm_campaign=enterprise&utm_term=repo
@@ -2,6 +2,10 @@
2
2
 
3
3
  module OAuth2
4
4
  class AccessToken # rubocop:disable Metrics/ClassLength
5
+ TOKEN_KEYS_STR = %w[access_token id_token token accessToken idToken].freeze
6
+ TOKEN_KEYS_SYM = %i[access_token id_token token accessToken idToken].freeze
7
+ TOKEN_KEY_LOOKUP = TOKEN_KEYS_STR + TOKEN_KEYS_SYM
8
+
5
9
  attr_reader :client, :token, :expires_in, :expires_at, :expires_latency, :params
6
10
  attr_accessor :options, :refresh_token, :response
7
11
 
@@ -13,13 +17,12 @@ module OAuth2
13
17
  # @option hash [String] 'access_token', 'id_token', 'token', :access_token, :id_token, or :token the access token
14
18
  # @return [AccessToken] the initialized AccessToken
15
19
  def from_hash(client, hash)
16
- hash = hash.dup
17
- token = hash.delete('access_token') || hash.delete(:access_token) ||
18
- hash.delete('id_token') || hash.delete(:id_token) ||
19
- hash.delete('token') || hash.delete(:token) ||
20
- hash.delete('accessToken') || hash.delete(:accessToken) ||
21
- hash.delete('idToken') || hash.delete(:idToken)
22
- new(client, token, hash)
20
+ fresh = hash.dup
21
+ supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
22
+ key = supported_keys[0]
23
+ extra_tokens_warning(supported_keys, key)
24
+ token = fresh.delete(key)
25
+ new(client, token, fresh)
23
26
  end
24
27
 
25
28
  # Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
@@ -30,12 +33,22 @@ module OAuth2
30
33
  def from_kvform(client, kvform)
31
34
  from_hash(client, Rack::Utils.parse_query(kvform))
32
35
  end
36
+
37
+ private
38
+
39
+ # Having too many is sus, and may lead to bugs. Having none is fine (e.g. refresh flow doesn't need a token).
40
+ def extra_tokens_warning(supported_keys, key)
41
+ return if OAuth2.config.silence_extra_tokens_warning
42
+ return if supported_keys.length <= 1
43
+
44
+ warn("OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (#{supported_keys}); using #{key.inspect}.")
45
+ end
33
46
  end
34
47
 
35
48
  # Initialize an AccessToken
36
49
  #
37
50
  # @param [Client] client the OAuth2::Client instance
38
- # @param [String] token the Access Token value
51
+ # @param [String] token the Access Token value (optional, may not be used in refresh flows)
39
52
  # @param [Hash] opts the options to create the Access Token with
40
53
  # @option opts [String] :refresh_token (nil) the refresh_token value
41
54
  # @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
@@ -50,14 +63,19 @@ module OAuth2
50
63
  @client = client
51
64
  @token = token.to_s
52
65
 
53
- if @client.options[:raise_errors] && (@token.nil? || @token.empty?)
54
- error = Error.new(opts)
55
- raise(error)
56
- end
57
66
  opts = opts.dup
58
67
  %i[refresh_token expires_in expires_at expires_latency].each do |arg|
59
68
  instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
60
69
  end
70
+ no_tokens = (@token.nil? || @token.empty?) && (@refresh_token.nil? || @refresh_token.empty?)
71
+ if no_tokens
72
+ if @client.options[:raise_errors]
73
+ error = Error.new(opts)
74
+ raise(error)
75
+ else
76
+ warn('OAuth2::AccessToken has no token')
77
+ end
78
+ end
61
79
  @expires_in ||= opts.delete('expires')
62
80
  @expires_in &&= @expires_in.to_i
63
81
  @expires_at &&= convert_expires_at(@expires_at)
data/lib/oauth2/client.rb CHANGED
@@ -157,46 +157,50 @@ module OAuth2
157
157
  def get_token(params, access_token_opts = {}, extract_access_token = nil, &block)
158
158
  warn('OAuth2::Client#get_token argument `extract_access_token` will be removed in oauth2 v3. Refactor to use `access_token_class` on #initialize.') if extract_access_token
159
159
  extract_access_token ||= options[:extract_access_token]
160
- params = params.map do |key, value|
161
- if RESERVED_PARAM_KEYS.include?(key)
162
- [key.to_sym, value]
163
- else
164
- [key, value]
165
- end
166
- end.to_h
167
-
168
- parse = params.key?(:parse) ? params.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
169
- snaky = params.key?(:snaky) ? params.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky]
160
+ parse, snaky, params, headers = parse_snaky_params_headers(params)
170
161
 
171
162
  request_opts = {
172
163
  raise_errors: options[:raise_errors],
173
164
  parse: parse,
174
165
  snaky: snaky,
175
166
  }
176
- params = authenticator.apply(params)
177
- headers = params.delete(:headers) || {}
178
167
  if options[:token_method] == :post
179
- request_opts[:body] = params
168
+
169
+ # NOTE: If proliferation of request types continues we should implement a parser solution for Request,
170
+ # just like we have with Response.
171
+ request_opts[:body] = if headers['Content-Type'] == 'application/json'
172
+ params.to_json
173
+ else
174
+ params
175
+ end
176
+
180
177
  request_opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
181
178
  else
182
179
  request_opts[:params] = params
183
180
  request_opts[:headers] = {}
184
181
  end
185
182
  request_opts[:headers].merge!(headers)
186
- http_method = options[:token_method]
187
- http_method = :post if http_method == :post_with_query_string
188
183
  response = request(http_method, token_url, request_opts, &block)
189
184
 
190
185
  # In v1.4.x, the deprecated extract_access_token option retrieves the token from the response.
191
186
  # We preserve this behavior here, but a custom access_token_class that implements #from_hash
192
187
  # should be used instead.
193
188
  if extract_access_token
194
- parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
189
+ parse_response_legacy(response, access_token_opts, extract_access_token)
195
190
  else
196
191
  parse_response(response, access_token_opts)
197
192
  end
198
193
  end
199
194
 
195
+ # The HTTP Method of the request
196
+ # @return [Symbol] HTTP verb, one of :get, :post, :put, :delete
197
+ def http_method
198
+ http_meth = options[:token_method].to_sym
199
+ return :post if http_meth == :post_with_query_string
200
+
201
+ http_meth
202
+ end
203
+
200
204
  # The Authorization Code strategy
201
205
  #
202
206
  # @see http://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-15#section-4.1
@@ -255,6 +259,22 @@ module OAuth2
255
259
 
256
260
  private
257
261
 
262
+ def parse_snaky_params_headers(params)
263
+ params = params.map do |key, value|
264
+ if RESERVED_PARAM_KEYS.include?(key)
265
+ [key.to_sym, value]
266
+ else
267
+ [key, value]
268
+ end
269
+ end.to_h
270
+ parse = params.key?(:parse) ? params.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
271
+ snaky = params.key?(:snaky) ? params.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky]
272
+ params = authenticator.apply(params)
273
+ # authenticator may add :headers, and we remove them here
274
+ headers = params.delete(:headers) || {}
275
+ [parse, snaky, params, headers]
276
+ end
277
+
258
278
  def execute_request(verb, url, opts = {})
259
279
  url = connection.build_url(url).to_s
260
280
 
@@ -282,8 +302,8 @@ module OAuth2
282
302
  Authenticator.new(id, secret, options[:auth_scheme])
283
303
  end
284
304
 
285
- def parse_response_with_legacy_extract(response, access_token_opts, extract_access_token)
286
- access_token = build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
305
+ def parse_response_legacy(response, access_token_opts, extract_access_token)
306
+ access_token = build_access_token_legacy(response, access_token_opts, extract_access_token)
287
307
 
288
308
  return access_token if access_token
289
309
 
@@ -321,7 +341,7 @@ module OAuth2
321
341
  # Builds the access token from the response of the HTTP call with legacy extract_access_token
322
342
  #
323
343
  # @return [AccessToken] the initialized AccessToken
324
- def build_access_token_legacy_extract(response, access_token_opts, extract_access_token)
344
+ def build_access_token_legacy(response, access_token_opts, extract_access_token)
325
345
  extract_access_token.call(self, response.parsed.merge(access_token_opts))
326
346
  rescue StandardError
327
347
  nil
@@ -46,7 +46,7 @@ module OAuth2
46
46
  # @param [Symbol] parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded),
47
47
  # :json, or :automatic (determined by Content-Type response header)
48
48
  # @param [true, false] snaky (true) Convert @parsed to a snake-case,
49
- # indifferent-access OAuth2::SnakyHash, which is a subclass of Hashie::Mash::Rash (from rash_alt gem)?
49
+ # indifferent-access SnakyHash::StringKeyed, which is a subclass of Hashie::Mash (from hashie gem)?
50
50
  # @param [Hash] options all other options for initializing the instance
51
51
  def initialize(response, parse: :automatic, snaky: true, **options)
52
52
  @response = response
@@ -90,7 +90,7 @@ module OAuth2
90
90
  end
91
91
  end
92
92
 
93
- @parsed = OAuth2::SnakyHash.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)
93
+ @parsed = SnakyHash::StringKeyed.new(@parsed) if options[:snaky] && @parsed.is_a?(Hash)
94
94
 
95
95
  @parsed
96
96
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OAuth2
4
4
  module Version
5
- VERSION = '2.0.5'.freeze
5
+ VERSION = '2.0.8'.freeze
6
6
  end
7
7
  end
data/lib/oauth2.rb CHANGED
@@ -5,13 +5,12 @@ require 'cgi'
5
5
  require 'time'
6
6
 
7
7
  # third party gems
8
- require 'rash'
8
+ require 'snaky_hash'
9
9
  require 'version_gem'
10
10
 
11
11
  # includes gem files
12
12
  require 'oauth2/version'
13
13
  require 'oauth2/error'
14
- require 'oauth2/snaky_hash'
15
14
  require 'oauth2/authenticator'
16
15
  require 'oauth2/client'
17
16
  require 'oauth2/strategy/base'
@@ -25,6 +24,15 @@ require 'oauth2/response'
25
24
 
26
25
  # The namespace of this library
27
26
  module OAuth2
27
+ DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(silence_extra_tokens_warning: false)
28
+ @config = DEFAULT_CONFIG.dup
29
+ class << self
30
+ attr_accessor :config
31
+ end
32
+ def configure
33
+ yield @config
34
+ end
35
+ module_function :configure
28
36
  end
29
37
 
30
38
  OAuth2::Version.class_eval do
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oauth2
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Boling
8
8
  - Erik Michaels-Ober
9
9
  - Michael Bleigh
10
- autorequire:
10
+ autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2022-07-07 00:00:00.000000000 Z
13
+ date: 2022-09-01 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: faraday
@@ -87,25 +87,19 @@ dependencies:
87
87
  - !ruby/object:Gem::Version
88
88
  version: '3'
89
89
  - !ruby/object:Gem::Dependency
90
- name: rash_alt
90
+ name: snaky_hash
91
91
  requirement: !ruby/object:Gem::Requirement
92
92
  requirements:
93
- - - ">="
94
- - !ruby/object:Gem::Version
95
- version: '0.4'
96
- - - "<"
93
+ - - "~>"
97
94
  - !ruby/object:Gem::Version
98
- version: '1'
95
+ version: '2.0'
99
96
  type: :runtime
100
97
  prerelease: false
101
98
  version_requirements: !ruby/object:Gem::Requirement
102
99
  requirements:
103
- - - ">="
104
- - !ruby/object:Gem::Version
105
- version: '0.4'
106
- - - "<"
100
+ - - "~>"
107
101
  - !ruby/object:Gem::Version
108
- version: '1'
102
+ version: '2.0'
109
103
  - !ruby/object:Gem::Dependency
110
104
  name: version_gem
111
105
  requirement: !ruby/object:Gem::Requirement
@@ -294,7 +288,6 @@ files:
294
288
  - lib/oauth2/client.rb
295
289
  - lib/oauth2/error.rb
296
290
  - lib/oauth2/response.rb
297
- - lib/oauth2/snaky_hash.rb
298
291
  - lib/oauth2/strategy/assertion.rb
299
292
  - lib/oauth2/strategy/auth_code.rb
300
293
  - lib/oauth2/strategy/base.rb
@@ -307,15 +300,15 @@ licenses:
307
300
  - MIT
308
301
  metadata:
309
302
  homepage_uri: https://github.com/oauth-xx/oauth2
310
- source_code_uri: https://github.com/oauth-xx/oauth2/tree/v2.0.5
311
- changelog_uri: https://github.com/oauth-xx/oauth2/blob/v2.0.5/CHANGELOG.md
303
+ source_code_uri: https://github.com/oauth-xx/oauth2/tree/v2.0.8
304
+ changelog_uri: https://github.com/oauth-xx/oauth2/blob/v2.0.8/CHANGELOG.md
312
305
  bug_tracker_uri: https://github.com/oauth-xx/oauth2/issues
313
- documentation_uri: https://www.rubydoc.info/gems/oauth2/2.0.5
306
+ documentation_uri: https://www.rubydoc.info/gems/oauth2/2.0.8
314
307
  wiki_uri: https://github.com/oauth-xx/oauth2/wiki
315
308
  rubygems_mfa_required: 'true'
316
309
  post_install_message: |2+
317
310
 
318
- You have installed oauth2 version OAuth2::Version, congratulations!
311
+ You have installed oauth2 version 2.0.8, congratulations!
319
312
 
320
313
  There are BREAKING changes, but most will not encounter them, and updating your code should be easy!
321
314
 
@@ -339,8 +332,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
339
332
  - !ruby/object:Gem::Version
340
333
  version: '0'
341
334
  requirements: []
342
- rubygems_version: 3.3.16
343
- signing_key:
335
+ rubygems_version: 3.3.21
336
+ signing_key:
344
337
  specification_version: 4
345
338
  summary: A Ruby wrapper for the OAuth 2.0 protocol.
346
339
  test_files: []
340
+ ...
@@ -1,8 +0,0 @@
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