oauth2 2.0.10 → 2.0.17
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +632 -286
- data/CITATION.cff +20 -0
- data/CODE_OF_CONDUCT.md +24 -23
- data/CONTRIBUTING.md +136 -46
- data/FUNDING.md +77 -0
- data/LICENSE.txt +2 -2
- data/OIDC.md +158 -0
- data/README.md +1142 -394
- data/REEK +0 -0
- data/RUBOCOP.md +71 -0
- data/SECURITY.md +11 -16
- data/lib/oauth2/access_token.rb +51 -7
- data/lib/oauth2/authenticator.rb +30 -1
- data/lib/oauth2/client.rb +35 -15
- data/lib/oauth2/error.rb +21 -3
- data/lib/oauth2/filtered_attributes.rb +21 -0
- data/lib/oauth2/response.rb +63 -31
- data/lib/oauth2/strategy/assertion.rb +4 -1
- data/lib/oauth2/strategy/auth_code.rb +10 -0
- data/lib/oauth2/strategy/base.rb +0 -0
- data/lib/oauth2/strategy/implicit.rb +8 -0
- data/lib/oauth2/strategy/password.rb +8 -0
- data/lib/oauth2/version.rb +1 -1
- data/lib/oauth2.rb +36 -0
- data/sig/oauth2/access_token.rbs +25 -0
- data/sig/oauth2/authenticator.rbs +22 -0
- data/sig/oauth2/client.rbs +52 -0
- data/sig/oauth2/error.rbs +8 -0
- data/sig/oauth2/filtered_attributes.rbs +6 -0
- data/sig/oauth2/response.rbs +18 -0
- data/sig/oauth2/strategy.rbs +34 -0
- data/sig/oauth2/version.rbs +5 -0
- data/sig/oauth2.rbs +9 -0
- data.tar.gz.sig +0 -0
- metadata +183 -87
- metadata.gz.sig +0 -0
data/REEK
ADDED
|
File without changes
|
data/RUBOCOP.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# RuboCop Usage Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A tale of two RuboCop plugin gems.
|
|
6
|
+
|
|
7
|
+
### RuboCop Gradual
|
|
8
|
+
|
|
9
|
+
This project uses `rubocop_gradual` instead of vanilla RuboCop for code style checking. The `rubocop_gradual` tool allows for gradual adoption of RuboCop rules by tracking violations in a lock file.
|
|
10
|
+
|
|
11
|
+
### RuboCop LTS
|
|
12
|
+
|
|
13
|
+
This project uses `rubocop-lts` to ensure, on a best-effort basis, compatibility with Ruby >= 1.9.2.
|
|
14
|
+
RuboCop rules are meticulously configured by the `rubocop-lts` family of gems to ensure that a project is compatible with a specific version of Ruby. See: https://rubocop-lts.gitlab.io for more.
|
|
15
|
+
|
|
16
|
+
## Checking RuboCop Violations
|
|
17
|
+
|
|
18
|
+
To check for RuboCop violations in this project, always use:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bundle exec rake rubocop_gradual:check
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**Do not use** the standard RuboCop commands like:
|
|
25
|
+
- `bundle exec rubocop`
|
|
26
|
+
- `rubocop`
|
|
27
|
+
|
|
28
|
+
## Understanding the Lock File
|
|
29
|
+
|
|
30
|
+
The `.rubocop_gradual.lock` file tracks all current RuboCop violations in the project. This allows the team to:
|
|
31
|
+
|
|
32
|
+
1. Prevent new violations while gradually fixing existing ones
|
|
33
|
+
2. Track progress on code style improvements
|
|
34
|
+
3. Ensure CI builds don't fail due to pre-existing violations
|
|
35
|
+
|
|
36
|
+
## Common Commands
|
|
37
|
+
|
|
38
|
+
- **Check violations**
|
|
39
|
+
- `bundle exec rake rubocop_gradual`
|
|
40
|
+
- `bundle exec rake rubocop_gradual:check`
|
|
41
|
+
- **(Safe) Autocorrect violations, and update lockfile if no new violations**
|
|
42
|
+
- `bundle exec rake rubocop_gradual:autocorrect`
|
|
43
|
+
- **Force update the lock file (w/o autocorrect) to match violations present in code**
|
|
44
|
+
- `bundle exec rake rubocop_gradual:force_update`
|
|
45
|
+
|
|
46
|
+
## Workflow
|
|
47
|
+
|
|
48
|
+
1. Before submitting a PR, run `bundle exec rake rubocop_gradual:autocorrect`
|
|
49
|
+
a. or just the default `bundle exec rake`, as autocorrection is a pre-requisite of the default task.
|
|
50
|
+
2. If there are new violations, either:
|
|
51
|
+
- Fix them in your code
|
|
52
|
+
- Run `bundle exec rake rubocop_gradual:force_update` to update the lock file (only for violations you can't fix immediately)
|
|
53
|
+
3. Commit the updated `.rubocop_gradual.lock` file along with your changes
|
|
54
|
+
|
|
55
|
+
## Never add inline RuboCop disables
|
|
56
|
+
|
|
57
|
+
Do not add inline `rubocop:disable` / `rubocop:enable` comments anywhere in the codebase (including specs, except when following the few existing `rubocop:disable` patterns for a rule already being disabled elsewhere in the code). We handle exceptions in two supported ways:
|
|
58
|
+
|
|
59
|
+
- Permanent/structural exceptions: prefer adjusting the RuboCop configuration (e.g., in `.rubocop.yml`) to exclude a rule for a path or file pattern when it makes sense project-wide.
|
|
60
|
+
- Temporary exceptions while improving code: record the current violations in `.rubocop_gradual.lock` via the gradual workflow:
|
|
61
|
+
- `bundle exec rake rubocop_gradual:autocorrect` (preferred; will autocorrect what it can and update the lock only if no new violations were introduced)
|
|
62
|
+
- If needed, `bundle exec rake rubocop_gradual:force_update` (as a last resort when you cannot fix the newly reported violations immediately)
|
|
63
|
+
|
|
64
|
+
In general, treat the rules as guidance to follow; fix violations rather than ignore them. For example, RSpec conventions in this project expect `described_class` to be used in specs that target a specific class under test.
|
|
65
|
+
|
|
66
|
+
## Benefits of rubocop_gradual
|
|
67
|
+
|
|
68
|
+
- Allows incremental adoption of code style rules
|
|
69
|
+
- Prevents CI failures due to pre-existing violations
|
|
70
|
+
- Provides a clear record of code style debt
|
|
71
|
+
- Enables focused efforts on improving code quality over time
|
data/SECURITY.md
CHANGED
|
@@ -2,25 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Supported Versions
|
|
4
4
|
|
|
5
|
-
| Version | Supported |
|
|
6
|
-
|
|
7
|
-
|
|
|
8
|
-
| 1.latest | ✅ | 10/2025 | [Tidelift Subscription][tidelift-ref] |
|
|
9
|
-
| <= 1 | ⛔ | ⛔ | ⛔ |
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|----------|-----------|
|
|
7
|
+
| 1.latest | ✅ |
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
## Security contact information
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## Reporting a Vulnerability
|
|
16
|
-
|
|
17
|
-
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security).
|
|
11
|
+
To report a security vulnerability, please use the
|
|
12
|
+
[Tidelift security contact](https://tidelift.com/security).
|
|
18
13
|
Tidelift will coordinate the fix and disclosure.
|
|
19
14
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
Available as part of the Tidelift Subscription.
|
|
15
|
+
## Additional Support
|
|
23
16
|
|
|
24
|
-
|
|
17
|
+
If you are interested in support for versions older than the latest release,
|
|
18
|
+
please consider sponsoring the project / maintainer @ https://liberapay.com/pboling/donate,
|
|
19
|
+
or find other sponsorship links in the [README].
|
|
25
20
|
|
|
26
|
-
[
|
|
21
|
+
[README]: README.md
|
data/lib/oauth2/access_token.rb
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# :nocov:
|
|
4
|
+
begin
|
|
5
|
+
# The first version of hashie that has a version file was 1.1.0
|
|
6
|
+
# The first version of hashie that required the version file at runtime was 3.2.0
|
|
7
|
+
# If it has already been loaded then this is very low cost, as Kernel.require uses maintains a cache
|
|
8
|
+
# If this it hasn't this will work to get it loaded, and then we will be able to use
|
|
9
|
+
# defined?(Hashie::Version)
|
|
10
|
+
# as a test.
|
|
11
|
+
# TODO: get rid this mess when we drop Hashie < 3.2, as Hashie will self-load its version then
|
|
12
|
+
require "hashie/version"
|
|
13
|
+
rescue LoadError
|
|
14
|
+
nil
|
|
15
|
+
end
|
|
16
|
+
# :nocov:
|
|
17
|
+
|
|
3
18
|
module OAuth2
|
|
4
19
|
class AccessToken # rubocop:disable Metrics/ClassLength
|
|
5
20
|
TOKEN_KEYS_STR = %w[access_token id_token token accessToken idToken].freeze
|
|
@@ -54,7 +69,16 @@ module OAuth2
|
|
|
54
69
|
extra_tokens_warning(supported_keys, t_key)
|
|
55
70
|
t_key
|
|
56
71
|
end
|
|
57
|
-
|
|
72
|
+
# :nocov:
|
|
73
|
+
# TODO: Get rid of this branching logic when dropping Hashie < v3.2
|
|
74
|
+
token = if !defined?(Hashie::VERSION) # i.e. <= "1.1.0"; the first Hashie to ship with a VERSION constant
|
|
75
|
+
warn("snaky_hash and oauth2 will drop support for Hashie v0 in the next major version. Please upgrade to a modern Hashie.")
|
|
76
|
+
# There is a bug in Hashie v0, which is accounts for.
|
|
77
|
+
fresh.delete(key) || fresh[key] || ""
|
|
78
|
+
else
|
|
79
|
+
fresh.delete(key) || ""
|
|
80
|
+
end
|
|
81
|
+
# :nocov:
|
|
58
82
|
new(client, token, fresh)
|
|
59
83
|
end
|
|
60
84
|
|
|
@@ -108,9 +132,15 @@ You may need to set `snaky: false`. See inline documentation for more info.
|
|
|
108
132
|
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
|
109
133
|
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
|
110
134
|
# @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
|
|
111
|
-
# @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
|
|
112
|
-
# one of :header, :body or :query
|
|
135
|
+
# @option opts [Symbol, Hash, or callable] :mode (:header) the transmission mode of the Access Token parameter value:
|
|
136
|
+
# either one of :header, :body or :query; or a Hash with verb symbols as keys mapping to one of these symbols
|
|
137
|
+
# (e.g., {get: :query, post: :header, delete: :header}); or a callable that accepts a request-verb parameter
|
|
138
|
+
# and returns one of these three symbols.
|
|
113
139
|
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
|
|
140
|
+
#
|
|
141
|
+
# @example Verb-dependent Hash mode
|
|
142
|
+
# # Send token in query for GET, in header for POST/DELETE, in body for PUT/PATCH
|
|
143
|
+
# OAuth2::AccessToken.new(client, token, mode: {get: :query, post: :header, delete: :header, put: :body, patch: :body})
|
|
114
144
|
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
|
|
115
145
|
# Access Token value in :body or :query transmission mode
|
|
116
146
|
# @option opts [String] :token_name (nil) the name of the response parameter that identifies the access token
|
|
@@ -300,7 +330,7 @@ You may need to set `snaky: false`. See inline documentation for more info.
|
|
|
300
330
|
#
|
|
301
331
|
# @see OAuth2::Client#request
|
|
302
332
|
def request(verb, path, opts = {}, &block)
|
|
303
|
-
configure_authentication!(opts)
|
|
333
|
+
configure_authentication!(opts, verb)
|
|
304
334
|
@client.request(verb, path, opts, &block)
|
|
305
335
|
end
|
|
306
336
|
|
|
@@ -346,12 +376,26 @@ You may need to set `snaky: false`. See inline documentation for more info.
|
|
|
346
376
|
|
|
347
377
|
private
|
|
348
378
|
|
|
349
|
-
def configure_authentication!(opts)
|
|
350
|
-
|
|
379
|
+
def configure_authentication!(opts, verb)
|
|
380
|
+
mode_opt = options[:mode]
|
|
381
|
+
mode =
|
|
382
|
+
if mode_opt.respond_to?(:call)
|
|
383
|
+
mode_opt.call(verb)
|
|
384
|
+
elsif mode_opt.is_a?(Hash)
|
|
385
|
+
key = verb.to_sym
|
|
386
|
+
# Try symbol key first, then string key; default to :header when missing
|
|
387
|
+
mode_opt[key] || mode_opt[key.to_s] || :header
|
|
388
|
+
else
|
|
389
|
+
mode_opt
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
case mode
|
|
351
393
|
when :header
|
|
352
394
|
opts[:headers] ||= {}
|
|
353
395
|
opts[:headers].merge!(headers)
|
|
354
396
|
when :query
|
|
397
|
+
# OAuth 2.1 note: Bearer tokens in the query string are omitted from the spec due to security risks.
|
|
398
|
+
# Prefer the default :header mode whenever possible.
|
|
355
399
|
opts[:params] ||= {}
|
|
356
400
|
opts[:params][options[:param_name]] = token
|
|
357
401
|
when :body
|
|
@@ -363,7 +407,7 @@ You may need to set `snaky: false`. See inline documentation for more info.
|
|
|
363
407
|
end
|
|
364
408
|
# @todo support for multi-part (file uploads)
|
|
365
409
|
else
|
|
366
|
-
raise("invalid :mode option of #{
|
|
410
|
+
raise("invalid :mode option of #{mode}")
|
|
367
411
|
end
|
|
368
412
|
end
|
|
369
413
|
|
data/lib/oauth2/authenticator.rb
CHANGED
|
@@ -3,12 +3,24 @@
|
|
|
3
3
|
require "base64"
|
|
4
4
|
|
|
5
5
|
module OAuth2
|
|
6
|
+
# Builds and applies client authentication to token and revoke requests.
|
|
7
|
+
#
|
|
8
|
+
# Depending on the selected mode, credentials are applied as Basic Auth
|
|
9
|
+
# headers, request body parameters, or only the client_id is sent (TLS).
|
|
6
10
|
class Authenticator
|
|
7
11
|
include FilteredAttributes
|
|
8
12
|
|
|
13
|
+
# @return [Symbol, String] Authentication mode (e.g., :basic_auth, :request_body, :tls_client_auth, :private_key_jwt)
|
|
14
|
+
# @return [String, nil] Client identifier
|
|
15
|
+
# @return [String, nil] Client secret (filtered in inspected output)
|
|
9
16
|
attr_reader :mode, :id, :secret
|
|
10
17
|
filtered_attributes :secret
|
|
11
18
|
|
|
19
|
+
# Create a new Authenticator
|
|
20
|
+
#
|
|
21
|
+
# @param [String, nil] id Client identifier
|
|
22
|
+
# @param [String, nil] secret Client secret
|
|
23
|
+
# @param [Symbol, String] mode Authentication mode
|
|
12
24
|
def initialize(id, secret, mode)
|
|
13
25
|
@id = id
|
|
14
26
|
@secret = secret
|
|
@@ -39,6 +51,11 @@ module OAuth2
|
|
|
39
51
|
end
|
|
40
52
|
end
|
|
41
53
|
|
|
54
|
+
# Encodes a Basic Authorization header value for the provided credentials.
|
|
55
|
+
#
|
|
56
|
+
# @param [String] user The client identifier
|
|
57
|
+
# @param [String] password The client secret
|
|
58
|
+
# @return [String] The value to use for the Authorization header
|
|
42
59
|
def self.encode_basic_auth(user, password)
|
|
43
60
|
"Basic #{Base64.strict_encode64("#{user}:#{password}")}"
|
|
44
61
|
end
|
|
@@ -47,6 +64,9 @@ module OAuth2
|
|
|
47
64
|
|
|
48
65
|
# Adds client_id and client_secret request parameters if they are not
|
|
49
66
|
# already set.
|
|
67
|
+
#
|
|
68
|
+
# @param [Hash] params Request parameters
|
|
69
|
+
# @return [Hash] Updated parameters including client_id and client_secret
|
|
50
70
|
def apply_params_auth(params)
|
|
51
71
|
result = {}
|
|
52
72
|
result["client_id"] = id unless id.nil?
|
|
@@ -54,8 +74,11 @@ module OAuth2
|
|
|
54
74
|
result.merge(params)
|
|
55
75
|
end
|
|
56
76
|
|
|
57
|
-
# When using schemes that don't require the client_secret to be passed
|
|
77
|
+
# When using schemes that don't require the client_secret to be passed (e.g., TLS Client Auth),
|
|
58
78
|
# we don't want to send the secret
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] params Request parameters
|
|
81
|
+
# @return [Hash] Updated parameters including only client_id
|
|
59
82
|
def apply_client_id(params)
|
|
60
83
|
result = {}
|
|
61
84
|
result["client_id"] = id unless id.nil?
|
|
@@ -64,13 +87,19 @@ module OAuth2
|
|
|
64
87
|
|
|
65
88
|
# Adds an `Authorization` header with Basic Auth credentials if and only if
|
|
66
89
|
# it is not already set in the params.
|
|
90
|
+
#
|
|
91
|
+
# @param [Hash] params Request parameters (may include :headers)
|
|
92
|
+
# @return [Hash] Updated parameters with Authorization header
|
|
67
93
|
def apply_basic_auth(params)
|
|
68
94
|
headers = params.fetch(:headers, {})
|
|
69
95
|
headers = basic_auth_header.merge(headers)
|
|
70
96
|
params.merge(headers: headers)
|
|
71
97
|
end
|
|
72
98
|
|
|
99
|
+
# Build the Basic Authorization header.
|
|
100
|
+
#
|
|
73
101
|
# @see https://datatracker.ietf.org/doc/html/rfc2617#section-2
|
|
102
|
+
# @return [Hash] Header hash containing the Authorization entry
|
|
74
103
|
def basic_auth_header
|
|
75
104
|
{"Authorization" => self.class.encode_basic_auth(id, secret)}
|
|
76
105
|
end
|
data/lib/oauth2/client.rb
CHANGED
|
@@ -19,7 +19,7 @@ module OAuth2
|
|
|
19
19
|
# The OAuth2::Client class
|
|
20
20
|
class Client # rubocop:disable Metrics/ClassLength
|
|
21
21
|
RESERVED_REQ_KEYS = %w[body headers params redirect_count].freeze
|
|
22
|
-
RESERVED_PARAM_KEYS = (RESERVED_REQ_KEYS + %w[parse snaky token_method]).freeze
|
|
22
|
+
RESERVED_PARAM_KEYS = (RESERVED_REQ_KEYS + %w[parse snaky snaky_hash_klass token_method]).freeze
|
|
23
23
|
|
|
24
24
|
include FilteredAttributes
|
|
25
25
|
|
|
@@ -256,10 +256,10 @@ module OAuth2
|
|
|
256
256
|
# @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
|
|
257
257
|
def revoke_token(token, token_type_hint = nil, params = {}, &block)
|
|
258
258
|
params[:token_method] ||= :post_with_query_string
|
|
259
|
+
params[:token] = token
|
|
260
|
+
params[:token_type_hint] = token_type_hint if token_type_hint
|
|
261
|
+
|
|
259
262
|
req_opts = params_to_req_opts(params)
|
|
260
|
-
req_opts[:params] ||= {}
|
|
261
|
-
req_opts[:params][:token] = token
|
|
262
|
-
req_opts[:params][:token_type_hint] = token_type_hint if token_type_hint
|
|
263
263
|
|
|
264
264
|
request(http_method, revoke_url, req_opts, &block)
|
|
265
265
|
end
|
|
@@ -321,6 +321,9 @@ module OAuth2
|
|
|
321
321
|
# requesting authorization. If it is provided at authorization time it MUST
|
|
322
322
|
# also be provided with the token exchange request.
|
|
323
323
|
#
|
|
324
|
+
# OAuth 2.1 note: Authorization Servers must compare redirect URIs using exact string matching.
|
|
325
|
+
# This client simply forwards the configured redirect_uri; the exact-match validation happens server-side.
|
|
326
|
+
#
|
|
324
327
|
# Providing :redirect_uri to the OAuth2::Client instantiation will take
|
|
325
328
|
# care of managing this.
|
|
326
329
|
#
|
|
@@ -330,6 +333,7 @@ module OAuth2
|
|
|
330
333
|
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.3
|
|
331
334
|
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1
|
|
332
335
|
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
|
|
336
|
+
# @see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13
|
|
333
337
|
#
|
|
334
338
|
# @return [Hash] the params to add to a request or URL
|
|
335
339
|
def redirection_params
|
|
@@ -342,14 +346,26 @@ module OAuth2
|
|
|
342
346
|
|
|
343
347
|
private
|
|
344
348
|
|
|
345
|
-
#
|
|
349
|
+
# Processes request parameters and transforms them into request options
|
|
350
|
+
#
|
|
351
|
+
# @param [Hash] params the request parameters to process
|
|
352
|
+
# @option params [Symbol] :parse (:automatic) parsing strategy for the response
|
|
353
|
+
# @option params [Boolean] :snaky (true) whether to convert response keys to snake_case
|
|
354
|
+
# @option params [Class] :snaky_hash_klass (SnakyHash::StringKeyed) class to use for snake_case hash conversion
|
|
355
|
+
# @option params [Symbol] :token_method (:post) HTTP method to use for token request
|
|
356
|
+
# @option params [Hash] :headers Additional HTTP headers for the request
|
|
357
|
+
#
|
|
358
|
+
# @return [Hash] the processed request options
|
|
359
|
+
#
|
|
360
|
+
# @api private
|
|
346
361
|
def params_to_req_opts(params)
|
|
347
|
-
parse, snaky, token_method, params, headers = parse_snaky_params_headers(params)
|
|
362
|
+
parse, snaky, snaky_hash_klass, token_method, params, headers = parse_snaky_params_headers(params)
|
|
348
363
|
req_opts = {
|
|
349
364
|
raise_errors: options[:raise_errors],
|
|
350
365
|
token_method: token_method || options[:token_method],
|
|
351
366
|
parse: parse,
|
|
352
367
|
snaky: snaky,
|
|
368
|
+
snaky_hash_klass: snaky_hash_klass,
|
|
353
369
|
}
|
|
354
370
|
if req_opts[:token_method] == :post
|
|
355
371
|
# NOTE: If proliferation of request types continues, we should implement a parser solution for Request,
|
|
@@ -369,19 +385,22 @@ module OAuth2
|
|
|
369
385
|
req_opts
|
|
370
386
|
end
|
|
371
387
|
|
|
372
|
-
# Processes and transforms
|
|
388
|
+
# Processes and transforms parameters for OAuth requests
|
|
373
389
|
#
|
|
374
390
|
# @param [Hash] params the input parameters to process
|
|
375
|
-
# @option params [Symbol
|
|
391
|
+
# @option params [Symbol] :parse (:automatic) parsing strategy for the response
|
|
376
392
|
# @option params [Boolean] :snaky (true) whether to convert response keys to snake_case
|
|
393
|
+
# @option params [Class] :snaky_hash_klass (SnakyHash::StringKeyed) class to use for snake_case hash conversion
|
|
394
|
+
# @option params [Symbol] :token_method overrides the default token method for this request
|
|
377
395
|
# @option params [Hash] :headers HTTP headers for the request
|
|
378
396
|
#
|
|
379
|
-
# @return [Array<(Symbol, Boolean, Hash, Hash)>] Returns an array containing:
|
|
380
|
-
# -
|
|
381
|
-
# -
|
|
382
|
-
# -
|
|
383
|
-
# -
|
|
384
|
-
# -
|
|
397
|
+
# @return [Array<(Symbol, Boolean, Class, Symbol, Hash, Hash)>] Returns an array containing:
|
|
398
|
+
# - parse strategy (Symbol)
|
|
399
|
+
# - snaky flag for response key transformation (Boolean)
|
|
400
|
+
# - hash class for snake_case conversion (Class)
|
|
401
|
+
# - token method override (Symbol, nil)
|
|
402
|
+
# - processed parameters (Hash)
|
|
403
|
+
# - HTTP headers (Hash)
|
|
385
404
|
#
|
|
386
405
|
# @api private
|
|
387
406
|
def parse_snaky_params_headers(params)
|
|
@@ -394,11 +413,12 @@ module OAuth2
|
|
|
394
413
|
end.to_h
|
|
395
414
|
parse = params.key?(:parse) ? params.delete(:parse) : Response::DEFAULT_OPTIONS[:parse]
|
|
396
415
|
snaky = params.key?(:snaky) ? params.delete(:snaky) : Response::DEFAULT_OPTIONS[:snaky]
|
|
416
|
+
snaky_hash_klass = params.key?(:snaky_hash_klass) ? params.delete(:snaky_hash_klass) : Response::DEFAULT_OPTIONS[:snaky_hash_klass]
|
|
397
417
|
token_method = params.delete(:token_method) if params.key?(:token_method)
|
|
398
418
|
params = authenticator.apply(params)
|
|
399
419
|
# authenticator may add :headers, and we separate them from params here
|
|
400
420
|
headers = params.delete(:headers) || {}
|
|
401
|
-
[parse, snaky, token_method, params, headers]
|
|
421
|
+
[parse, snaky, snaky_hash_klass, token_method, params, headers]
|
|
402
422
|
end
|
|
403
423
|
|
|
404
424
|
# Executes an HTTP request with error handling and response processing
|
data/lib/oauth2/error.rb
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
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
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
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
|
|
12
20
|
if response.respond_to?(:parsed)
|
|
@@ -29,6 +37,11 @@ module OAuth2
|
|
|
29
37
|
|
|
30
38
|
private
|
|
31
39
|
|
|
40
|
+
# Builds a multi-line error message including description and raw body.
|
|
41
|
+
#
|
|
42
|
+
# @param [String, #encode] response_body Response body content
|
|
43
|
+
# @param [Hash] opts Options including :error_description
|
|
44
|
+
# @return [String] Message suitable for StandardError
|
|
32
45
|
def error_message(response_body, opts = {})
|
|
33
46
|
lines = []
|
|
34
47
|
|
|
@@ -46,6 +59,11 @@ module OAuth2
|
|
|
46
59
|
lines.join("\n")
|
|
47
60
|
end
|
|
48
61
|
|
|
62
|
+
# Formats the OAuth2 error code and description into a single string.
|
|
63
|
+
#
|
|
64
|
+
# @param [String, nil] code OAuth2 error code
|
|
65
|
+
# @param [String, nil] description OAuth2 error description
|
|
66
|
+
# @return [Hash] Options hash containing :error_description when present
|
|
49
67
|
def parse_error_description(code, description)
|
|
50
68
|
return {} unless code || description
|
|
51
69
|
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
module OAuth2
|
|
2
|
+
# Mixin that redacts sensitive instance variables in #inspect output.
|
|
3
|
+
#
|
|
4
|
+
# Classes include this module and declare which attributes should be filtered
|
|
5
|
+
# using {.filtered_attributes}. Any instance variable name that includes one of
|
|
6
|
+
# those attribute names will be shown as [FILTERED] in the object's inspect.
|
|
2
7
|
module FilteredAttributes
|
|
8
|
+
# Hook invoked when the module is included. Extends the including class with
|
|
9
|
+
# class-level helpers.
|
|
10
|
+
#
|
|
11
|
+
# @param [Class] base The including class
|
|
12
|
+
# @return [void]
|
|
3
13
|
def self.included(base)
|
|
4
14
|
base.extend(ClassMethods)
|
|
5
15
|
end
|
|
6
16
|
|
|
17
|
+
# Class-level helpers for configuring filtered attributes.
|
|
7
18
|
module ClassMethods
|
|
19
|
+
# Declare attributes that should be redacted in inspect output.
|
|
20
|
+
#
|
|
21
|
+
# @param [Array<Symbol, String>] attributes One or more attribute names
|
|
22
|
+
# @return [void]
|
|
8
23
|
def filtered_attributes(*attributes)
|
|
9
24
|
@filtered_attribute_names = attributes.map(&:to_sym)
|
|
10
25
|
end
|
|
11
26
|
|
|
27
|
+
# The configured attribute names to filter.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<Symbol>]
|
|
12
30
|
def filtered_attribute_names
|
|
13
31
|
@filtered_attribute_names || []
|
|
14
32
|
end
|
|
15
33
|
end
|
|
16
34
|
|
|
35
|
+
# Custom inspect that redacts configured attributes.
|
|
36
|
+
#
|
|
37
|
+
# @return [String]
|
|
17
38
|
def inspect
|
|
18
39
|
filtered_attribute_names = self.class.filtered_attribute_names
|
|
19
40
|
return super if filtered_attribute_names.empty?
|