oauth2 1.4.11 → 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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +956 -85
- data/CITATION.cff +20 -0
- data/CODE_OF_CONDUCT.md +24 -23
- data/CONTRIBUTING.md +265 -34
- data/FUNDING.md +70 -0
- data/LICENSE.md +111 -0
- data/{LICENSE → MIT.md} +6 -7
- data/README.md +1013 -327
- data/RUBOCOP.md +71 -0
- data/SECURITY.md +11 -16
- data/certs/pboling.pem +27 -0
- data/lib/oauth2/access_token.rb +286 -46
- data/lib/oauth2/auth_sanitizer.rb +20 -0
- data/lib/oauth2/authenticator.rb +48 -9
- data/lib/oauth2/client.rb +466 -132
- data/lib/oauth2/error.rb +63 -24
- data/lib/oauth2/filtered_attributes.rb +10 -0
- data/lib/oauth2/response.rb +140 -43
- data/lib/oauth2/strategy/assertion.rb +68 -40
- data/lib/oauth2/strategy/auth_code.rb +25 -4
- data/lib/oauth2/strategy/client_credentials.rb +3 -3
- data/lib/oauth2/strategy/implicit.rb +17 -2
- data/lib/oauth2/strategy/password.rb +14 -4
- data/lib/oauth2/version.rb +2 -59
- data/lib/oauth2.rb +101 -12
- 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 +11 -0
- data/sig/oauth2/response.rbs +18 -0
- data/sig/oauth2/sanitized_logger.rbs +32 -0
- data/sig/oauth2/strategy.rbs +34 -0
- data/sig/oauth2/thing_filter.rbs +10 -0
- data/sig/oauth2/version.rbs +6 -0
- data/sig/oauth2.rbs +9 -0
- data.tar.gz.sig +0 -0
- metadata +409 -83
- metadata.gz.sig +0 -0
- data/lib/oauth2/mac_token.rb +0 -130
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
|
-
| 2.latest | ✅ |
|
|
8
|
-
| 1.latest | ✅ | 04/2023 | [Tidelift Subscription][tidelift-ref] |
|
|
9
|
-
| <= 1 | ⛔ | ⛔ | ⛔ |
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
|----------|-----------|
|
|
7
|
+
| 2.0.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/certs/pboling.pem
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEgDCCAuigAwIBAgIBATANBgkqhkiG9w0BAQsFADBDMRUwEwYDVQQDDAxwZXRl
|
|
3
|
+
ci5ib2xpbmcxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkW
|
|
4
|
+
A2NvbTAeFw0yNTA1MDQxNTMzMDlaFw00NTA0MjkxNTMzMDlaMEMxFTATBgNVBAMM
|
|
5
|
+
DHBldGVyLmJvbGluZzEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPy
|
|
6
|
+
LGQBGRYDY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAruUoo0WA
|
|
7
|
+
uoNuq6puKWYeRYiZekz/nsDeK5x/0IEirzcCEvaHr3Bmz7rjo1I6On3gGKmiZs61
|
|
8
|
+
LRmQ3oxy77ydmkGTXBjruJB+pQEn7UfLSgQ0xa1/X3kdBZt6RmabFlBxnHkoaGY5
|
|
9
|
+
mZuZ5+Z7walmv6sFD9ajhzj+oIgwWfnEHkXYTR8I6VLN7MRRKGMPoZ/yvOmxb2DN
|
|
10
|
+
coEEHWKO9CvgYpW7asIihl/9GMpKiRkcYPm9dGQzZc6uTwom1COfW0+ZOFrDVBuV
|
|
11
|
+
FMQRPswZcY4Wlq0uEBLPU7hxnCL9nKK6Y9IhdDcz1mY6HZ91WImNslOSI0S8hRpj
|
|
12
|
+
yGOWxQIhBT3fqCBlRIqFQBudrnD9jSNpSGsFvbEijd5ns7Z9ZMehXkXDycpGAUj1
|
|
13
|
+
to/5cuTWWw1JqUWrKJYoifnVhtE1o1DZ+LkPtWxHtz5kjDG/zR3MG0Ula0UOavlD
|
|
14
|
+
qbnbcXPBnwXtTFeZ3C+yrWpE4pGnl3yGkZj9SMTlo9qnTMiPmuWKQDatAgMBAAGj
|
|
15
|
+
fzB9MAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBQE8uWvNbPVNRXZ
|
|
16
|
+
HlgPbc2PCzC4bjAhBgNVHREEGjAYgRZwZXRlci5ib2xpbmdAZ21haWwuY29tMCEG
|
|
17
|
+
A1UdEgQaMBiBFnBldGVyLmJvbGluZ0BnbWFpbC5jb20wDQYJKoZIhvcNAQELBQAD
|
|
18
|
+
ggGBAJbnUwfJQFPkBgH9cL7hoBfRtmWiCvdqdjeTmi04u8zVNCUox0A4gT982DE9
|
|
19
|
+
wmuN12LpdajxZONqbXuzZvc+nb0StFwmFYZG6iDwaf4BPywm2e/Vmq0YG45vZXGR
|
|
20
|
+
L8yMDSK1cQXjmA+ZBKOHKWavxP6Vp7lWvjAhz8RFwqF9GuNIdhv9NpnCAWcMZtpm
|
|
21
|
+
GUPyIWw/Cw/2wZp74QzZj6Npx+LdXoLTF1HMSJXZ7/pkxLCsB8m4EFVdb/IrW/0k
|
|
22
|
+
kNSfjtAfBHO8nLGuqQZVH9IBD1i9K6aSs7pT6TW8itXUIlkIUI2tg5YzW6OFfPzq
|
|
23
|
+
QekSkX3lZfY+HTSp/o+YvKkqWLUV7PQ7xh1ZYDtocpaHwgxe/j3bBqHE+CUPH2vA
|
|
24
|
+
0V/FwdTRWcwsjVoOJTrYcff8pBZ8r2MvtAc54xfnnhGFzeRHfcltobgFxkAXdE6p
|
|
25
|
+
DVjBtqT23eugOqQ73umLcYDZkc36vnqGxUBSsXrzY9pzV5gGr2I8YUxMqf6ATrZt
|
|
26
|
+
L9nRqA==
|
|
27
|
+
-----END CERTIFICATE-----
|
data/lib/oauth2/access_token.rb
CHANGED
|
@@ -1,59 +1,184 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# simplecov:disable
|
|
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
|
+
# simplecov:enable
|
|
17
|
+
|
|
3
18
|
module OAuth2
|
|
4
|
-
class AccessToken
|
|
5
|
-
|
|
6
|
-
|
|
19
|
+
class AccessToken # rubocop:disable Metrics/ClassLength
|
|
20
|
+
TOKEN_KEYS_STR = %w[access_token id_token token accessToken idToken].freeze
|
|
21
|
+
TOKEN_KEYS_SYM = %i[access_token id_token token accessToken idToken].freeze
|
|
22
|
+
TOKEN_KEY_LOOKUP = TOKEN_KEYS_STR + TOKEN_KEYS_SYM
|
|
23
|
+
|
|
24
|
+
include FilteredAttributes
|
|
25
|
+
|
|
26
|
+
attr_reader :client, :token, :expires_in, :expires_at, :expires_latency, :params
|
|
27
|
+
attr_accessor :options, :refresh_token, :response
|
|
28
|
+
filtered_attributes :token, :refresh_token
|
|
7
29
|
|
|
8
|
-
# Should these methods be deprecated?
|
|
9
30
|
class << self
|
|
10
31
|
# Initializes an AccessToken from a Hash
|
|
11
32
|
#
|
|
12
|
-
# @param [Client] the OAuth2::Client instance
|
|
13
|
-
# @param [Hash] a hash
|
|
14
|
-
# @
|
|
33
|
+
# @param [OAuth2::Client] client the OAuth2::Client instance
|
|
34
|
+
# @param [Hash] hash a hash containing the token and other properties
|
|
35
|
+
# @option hash [String] 'access_token' the access token value
|
|
36
|
+
# @option hash [String] 'id_token' alternative key for the access token value
|
|
37
|
+
# @option hash [String] 'token' alternative key for the access token value
|
|
38
|
+
# @option hash [String] 'refresh_token' (optional) the refresh token value
|
|
39
|
+
# @option hash [Integer, String] 'expires_in' (optional) number of seconds until token expires
|
|
40
|
+
# @option hash [Integer, String] 'expires_at' (optional) epoch time in seconds when token expires
|
|
41
|
+
# @option hash [Integer, String] 'expires_latency' (optional) seconds to reduce token validity by
|
|
42
|
+
#
|
|
43
|
+
# @return [OAuth2::AccessToken] the initialized AccessToken
|
|
44
|
+
#
|
|
45
|
+
# @note The method will use the first found token key in the following order:
|
|
46
|
+
# 'access_token', 'id_token', 'token' (or their symbolic versions)
|
|
47
|
+
# @note If multiple token keys are present, a warning will be issued unless
|
|
48
|
+
# OAuth2.config.silence_extra_tokens_warning is true
|
|
49
|
+
# @note If no token keys are present, a warning will be issued unless
|
|
50
|
+
# OAuth2.config.silence_no_tokens_warning is true
|
|
51
|
+
# @note For "soon-to-expire"/"clock-skew" functionality see the `:expires_latency` option.
|
|
52
|
+
# @note If snaky key conversion is being used, token_name needs to match the converted key.
|
|
53
|
+
#
|
|
54
|
+
# @example
|
|
55
|
+
# hash = { 'access_token' => 'token_value', 'refresh_token' => 'refresh_value' }
|
|
56
|
+
# access_token = OAuth2::AccessToken.from_hash(client, hash)
|
|
15
57
|
def from_hash(client, hash)
|
|
16
|
-
|
|
17
|
-
|
|
58
|
+
fresh = hash.dup
|
|
59
|
+
# If token_name is present, then use that key name
|
|
60
|
+
if fresh.key?(:token_name)
|
|
61
|
+
t_key = fresh[:token_name]
|
|
62
|
+
no_tokens_warning(fresh, t_key)
|
|
63
|
+
else
|
|
64
|
+
# Otherwise, if one of the supported default keys is present, use whichever has precedence
|
|
65
|
+
supported_keys = TOKEN_KEY_LOOKUP & fresh.keys
|
|
66
|
+
t_key = supported_keys[0]
|
|
67
|
+
extra_tokens_warning(supported_keys, t_key)
|
|
68
|
+
end
|
|
69
|
+
# simplecov:disable
|
|
70
|
+
# TODO: Get rid of this branching logic when dropping Hashie < v3.2
|
|
71
|
+
token = extract_token_value(fresh, t_key)
|
|
72
|
+
# simplecov:enable
|
|
73
|
+
new(client, token, fresh)
|
|
18
74
|
end
|
|
19
75
|
|
|
20
76
|
# Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
|
|
21
77
|
#
|
|
22
78
|
# @param [Client] client the OAuth2::Client instance
|
|
23
79
|
# @param [String] kvform the application/x-www-form-urlencoded string
|
|
24
|
-
# @return [AccessToken] the
|
|
80
|
+
# @return [AccessToken] the initialized AccessToken
|
|
25
81
|
def from_kvform(client, kvform)
|
|
26
82
|
from_hash(client, Rack::Utils.parse_query(kvform))
|
|
27
83
|
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Having too many is sus, and may lead to bugs. Having none is fine (e.g. refresh flow doesn't need a token).
|
|
88
|
+
def extra_tokens_warning(supported_keys, key)
|
|
89
|
+
return if OAuth2.config.silence_extra_tokens_warning
|
|
90
|
+
return if supported_keys.length <= 1
|
|
91
|
+
|
|
92
|
+
warn("OAuth2::AccessToken.from_hash: `hash` contained more than one 'token' key (#{supported_keys}); using #{key.inspect}.")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def no_tokens_warning(hash, key)
|
|
96
|
+
return if OAuth2.config.silence_no_tokens_warning
|
|
97
|
+
return if key && hash.key?(key)
|
|
98
|
+
|
|
99
|
+
warn(%[
|
|
100
|
+
OAuth2::AccessToken#from_hash key mismatch.
|
|
101
|
+
Custom token_name (#{key}) is not found in (#{hash.keys})
|
|
102
|
+
You may need to set `snaky: false`. See inline documentation for more info.
|
|
103
|
+
])
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# simplecov:disable
|
|
107
|
+
def extract_token_value(fresh, key)
|
|
108
|
+
token_value = fresh.delete(key)
|
|
109
|
+
return token_value || "" if defined?(Hashie::VERSION)
|
|
110
|
+
|
|
111
|
+
warn("snaky_hash and oauth2 will drop support for Hashie v0 in the next major version. Please upgrade to a modern Hashie.")
|
|
112
|
+
# There is a bug in Hashie v0, which this accounts for.
|
|
113
|
+
token_value || fresh[key] || ""
|
|
114
|
+
end
|
|
115
|
+
# simplecov:enable
|
|
28
116
|
end
|
|
29
117
|
|
|
30
|
-
#
|
|
118
|
+
# Initialize an AccessToken
|
|
119
|
+
#
|
|
120
|
+
# @note For "soon-to-expire"/"clock-skew" functionality see the `:expires_latency` option.
|
|
121
|
+
# @note If no token is provided, the AccessToken will be considered invalid.
|
|
122
|
+
# This is to prevent the possibility of a token being accidentally
|
|
123
|
+
# created with no token value.
|
|
124
|
+
# If you want to create an AccessToken with no token value,
|
|
125
|
+
# you can pass in an empty string or nil for the token value.
|
|
126
|
+
# If you want to create an AccessToken with no token value and
|
|
127
|
+
# no refresh token, you can pass in an empty string or nil for the
|
|
128
|
+
# token value and nil for the refresh token, and `raise_errors: false`.
|
|
31
129
|
#
|
|
32
130
|
# @param [Client] client the OAuth2::Client instance
|
|
33
|
-
# @param [String] token the Access Token value
|
|
131
|
+
# @param [String] token the Access Token value (optional, may not be used in refresh flows)
|
|
34
132
|
# @param [Hash] opts the options to create the Access Token with
|
|
35
133
|
# @option opts [String] :refresh_token (nil) the refresh_token value
|
|
36
134
|
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
|
|
37
135
|
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
|
|
38
|
-
# @option opts [
|
|
39
|
-
#
|
|
136
|
+
# @option opts [FixNum, String] :expires_latency (nil) the number of seconds by which AccessToken validity will be reduced to offset latency, @version 2.0+
|
|
137
|
+
# @option opts [Symbol, Hash, or callable] :mode (:header) the transmission mode of the Access Token parameter value:
|
|
138
|
+
# either one of :header, :body or :query; or a Hash with verb symbols as keys mapping to one of these symbols
|
|
139
|
+
# (e.g., `{get: :query, post: :header, delete: :header}`); or a callable that accepts a request-verb parameter
|
|
140
|
+
# and returns one of these three symbols.
|
|
40
141
|
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
|
|
142
|
+
#
|
|
143
|
+
# @example Verb-dependent Hash mode
|
|
144
|
+
# # Send token in query for GET, in header for POST/DELETE, in body for PUT/PATCH
|
|
145
|
+
# OAuth2::AccessToken.new(client, token, mode: {get: :query, post: :header, delete: :header, put: :body, patch: :body})
|
|
41
146
|
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
|
|
42
147
|
# Access Token value in :body or :query transmission mode
|
|
148
|
+
# @option opts [String] :token_name (nil) the name of the response parameter that identifies the access token
|
|
149
|
+
# When nil one of TOKEN_KEY_LOOKUP will be used
|
|
43
150
|
def initialize(client, token, opts = {})
|
|
44
151
|
@client = client
|
|
45
152
|
@token = token.to_s
|
|
46
153
|
opts = opts.dup
|
|
47
|
-
[
|
|
154
|
+
%i[refresh_token expires_in expires_at expires_latency].each do |arg|
|
|
48
155
|
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
|
|
49
156
|
end
|
|
50
|
-
@
|
|
157
|
+
no_tokens = (@token.nil? || @token.empty?) && (@refresh_token.nil? || @refresh_token.empty?)
|
|
158
|
+
if no_tokens
|
|
159
|
+
if @client.options[:raise_errors]
|
|
160
|
+
raise Error.new({
|
|
161
|
+
error: "OAuth2::AccessToken has no token",
|
|
162
|
+
error_description: "Options are: #{opts.inspect}",
|
|
163
|
+
})
|
|
164
|
+
elsif !OAuth2.config.silence_no_tokens_warning
|
|
165
|
+
warn("OAuth2::AccessToken has no token")
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
# @option opts [Fixnum, String] :expires is deprecated
|
|
169
|
+
@expires_in ||= opts.delete("expires")
|
|
51
170
|
@expires_in &&= @expires_in.to_i
|
|
52
171
|
@expires_at &&= convert_expires_at(@expires_at)
|
|
53
|
-
@
|
|
54
|
-
@
|
|
55
|
-
|
|
56
|
-
|
|
172
|
+
@expires_latency &&= @expires_latency.to_i
|
|
173
|
+
@expires_at ||= Time.now.to_i + @expires_in if @expires_in && !@expires_in.zero?
|
|
174
|
+
@expires_at -= @expires_latency if @expires_latency
|
|
175
|
+
@options = {
|
|
176
|
+
mode: opts.delete(:mode) || :header,
|
|
177
|
+
header_format: opts.delete(:header_format) || "Bearer %s",
|
|
178
|
+
param_name: opts.delete(:param_name) || "access_token",
|
|
179
|
+
}
|
|
180
|
+
@options[:token_name] = opts.delete(:token_name) if opts.key?(:token_name)
|
|
181
|
+
|
|
57
182
|
@params = opts
|
|
58
183
|
end
|
|
59
184
|
|
|
@@ -64,40 +189,131 @@ module OAuth2
|
|
|
64
189
|
@params[key]
|
|
65
190
|
end
|
|
66
191
|
|
|
67
|
-
# Whether
|
|
192
|
+
# Whether the token expires
|
|
68
193
|
#
|
|
69
194
|
# @return [Boolean]
|
|
70
195
|
def expires?
|
|
71
196
|
!!@expires_at
|
|
72
197
|
end
|
|
73
198
|
|
|
74
|
-
#
|
|
199
|
+
# Check if token is expired
|
|
75
200
|
#
|
|
76
|
-
# @return [Boolean]
|
|
201
|
+
# @return [Boolean] true if the token is expired, false otherwise
|
|
77
202
|
def expired?
|
|
78
|
-
expires? && (expires_at
|
|
203
|
+
expires? && (expires_at <= Time.now.to_i)
|
|
79
204
|
end
|
|
80
205
|
|
|
81
206
|
# Refreshes the current Access Token
|
|
82
207
|
#
|
|
83
|
-
# @
|
|
84
|
-
# @
|
|
85
|
-
|
|
86
|
-
|
|
208
|
+
# @param [Hash] params additional params to pass to the refresh token request
|
|
209
|
+
# @param [Hash] access_token_opts options that will be passed to the AccessToken initialization
|
|
210
|
+
#
|
|
211
|
+
# @yield [opts] The block to modify the refresh token request options
|
|
212
|
+
# @yieldparam [Hash] opts The options hash that can be modified
|
|
213
|
+
#
|
|
214
|
+
# @return [OAuth2::AccessToken] a new AccessToken instance
|
|
215
|
+
#
|
|
216
|
+
# @note current token's options are carried over to the new AccessToken
|
|
217
|
+
def refresh(params = {}, access_token_opts = {}, &block)
|
|
218
|
+
raise OAuth2::Error.new({error: "A refresh_token is not available"}) unless refresh_token
|
|
87
219
|
|
|
88
|
-
params[:grant_type] =
|
|
220
|
+
params[:grant_type] = "refresh_token"
|
|
89
221
|
params[:refresh_token] = refresh_token
|
|
90
|
-
new_token = @client.get_token(params)
|
|
222
|
+
new_token = @client.get_token(params, access_token_opts, &block)
|
|
91
223
|
new_token.options = options
|
|
92
|
-
|
|
224
|
+
if new_token.refresh_token
|
|
225
|
+
# Keep it if there is one
|
|
226
|
+
else
|
|
227
|
+
new_token.refresh_token = refresh_token
|
|
228
|
+
end
|
|
93
229
|
new_token
|
|
94
230
|
end
|
|
231
|
+
# A compatibility alias
|
|
232
|
+
# @note does not modify the receiver, so bang is not the default method
|
|
233
|
+
alias_method :refresh!, :refresh
|
|
234
|
+
|
|
235
|
+
# Revokes the token at the authorization server
|
|
236
|
+
#
|
|
237
|
+
# @param [Hash] params additional parameters to be sent during revocation
|
|
238
|
+
# @option params [String, Symbol, nil] :token_type_hint ('access_token' or 'refresh_token') hint about which token to revoke
|
|
239
|
+
# @option params [Symbol] :token_method (:post_with_query_string) overrides OAuth2::Client#options[:token_method]
|
|
240
|
+
#
|
|
241
|
+
# @yield [req] The block is passed the request being made, allowing customization
|
|
242
|
+
# @yieldparam [Faraday::Request] req The request object that can be modified
|
|
243
|
+
#
|
|
244
|
+
# @return [OAuth2::Response] OAuth2::Response instance
|
|
245
|
+
#
|
|
246
|
+
# @api public
|
|
247
|
+
#
|
|
248
|
+
# @raise [OAuth2::Error] if token_type_hint is invalid or the specified token is not available
|
|
249
|
+
#
|
|
250
|
+
# @note If the token passed to the request
|
|
251
|
+
# is an access token, the server MAY revoke the respective refresh
|
|
252
|
+
# token as well.
|
|
253
|
+
# @note If the token passed to the request
|
|
254
|
+
# is a refresh token and the authorization server supports the
|
|
255
|
+
# revocation of access tokens, then the authorization server SHOULD
|
|
256
|
+
# also invalidate all access tokens based on the same authorization
|
|
257
|
+
# grant
|
|
258
|
+
# @note If the server responds with HTTP status code 503, your code must
|
|
259
|
+
# assume the token still exists and may retry after a reasonable delay.
|
|
260
|
+
# The server may include a "Retry-After" header in the response to
|
|
261
|
+
# indicate how long the service is expected to be unavailable to the
|
|
262
|
+
# requesting client.
|
|
263
|
+
#
|
|
264
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7009
|
|
265
|
+
# @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
|
|
266
|
+
def revoke(params = {}, &block)
|
|
267
|
+
token_type_hint_orig = params.delete(:token_type_hint)
|
|
268
|
+
token_type_hint = nil
|
|
269
|
+
revoke_token = case token_type_hint_orig
|
|
270
|
+
when "access_token", :access_token
|
|
271
|
+
token_type_hint = "access_token"
|
|
272
|
+
token
|
|
273
|
+
when "refresh_token", :refresh_token
|
|
274
|
+
token_type_hint = "refresh_token"
|
|
275
|
+
refresh_token
|
|
276
|
+
when nil
|
|
277
|
+
if token
|
|
278
|
+
token_type_hint = "access_token"
|
|
279
|
+
token
|
|
280
|
+
elsif refresh_token
|
|
281
|
+
token_type_hint = "refresh_token"
|
|
282
|
+
refresh_token
|
|
283
|
+
end
|
|
284
|
+
else
|
|
285
|
+
raise OAuth2::Error.new({error: "token_type_hint must be one of [nil, :refresh_token, :access_token], so if you need something else consider using a subclass or entirely custom AccessToken class."})
|
|
286
|
+
end
|
|
287
|
+
raise OAuth2::Error.new({error: "#{token_type_hint || "unknown token type"} is not available for revoking"}) unless revoke_token && !revoke_token.empty?
|
|
288
|
+
|
|
289
|
+
@client.revoke_token(revoke_token, token_type_hint, params, &block)
|
|
290
|
+
end
|
|
291
|
+
# A compatibility alias
|
|
292
|
+
# @note does not modify the receiver, so bang is not the default method
|
|
293
|
+
alias_method :revoke!, :revoke
|
|
95
294
|
|
|
96
295
|
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
|
|
97
296
|
#
|
|
297
|
+
# @note Don't return expires_latency because it has already been deducted from expires_at
|
|
298
|
+
#
|
|
98
299
|
# @return [Hash] a hash of AccessToken property values
|
|
99
300
|
def to_hash
|
|
100
|
-
|
|
301
|
+
hsh = {
|
|
302
|
+
access_token: token,
|
|
303
|
+
refresh_token: refresh_token,
|
|
304
|
+
expires_at: expires_at,
|
|
305
|
+
mode: options[:mode],
|
|
306
|
+
header_format: options[:header_format],
|
|
307
|
+
param_name: options[:param_name],
|
|
308
|
+
}
|
|
309
|
+
hsh[:token_name] = options[:token_name] if options.key?(:token_name)
|
|
310
|
+
# TODO: Switch when dropping Ruby < 2.5 support
|
|
311
|
+
# params.transform_keys(&:to_sym) # Ruby 2.5 only
|
|
312
|
+
# Old Ruby transform_keys alternative:
|
|
313
|
+
sheesh = @params.each_with_object({}) { |(key, value), memo|
|
|
314
|
+
memo[key.to_sym] = value
|
|
315
|
+
}
|
|
316
|
+
sheesh.merge(hsh)
|
|
101
317
|
end
|
|
102
318
|
|
|
103
319
|
# Make a request with the Access Token
|
|
@@ -105,9 +321,18 @@ module OAuth2
|
|
|
105
321
|
# @param [Symbol] verb the HTTP request method
|
|
106
322
|
# @param [String] path the HTTP URL path of the request
|
|
107
323
|
# @param [Hash] opts the options to make the request with
|
|
108
|
-
# @
|
|
324
|
+
# @option opts [Hash] :params additional URL parameters
|
|
325
|
+
# @option opts [Hash, String] :body the request body
|
|
326
|
+
# @option opts [Hash] :headers request headers
|
|
327
|
+
#
|
|
328
|
+
# @yield [req] The block to modify the request
|
|
329
|
+
# @yieldparam [Faraday::Request] req The request object that can be modified
|
|
330
|
+
#
|
|
331
|
+
# @return [OAuth2::Response] the response from the request
|
|
332
|
+
#
|
|
333
|
+
# @see OAuth2::Client#request
|
|
109
334
|
def request(verb, path, opts = {}, &block)
|
|
110
|
-
configure_authentication!(opts)
|
|
335
|
+
configure_authentication!(opts, verb)
|
|
111
336
|
@client.request(verb, path, opts, &block)
|
|
112
337
|
end
|
|
113
338
|
|
|
@@ -148,29 +373,44 @@ module OAuth2
|
|
|
148
373
|
|
|
149
374
|
# Get the headers hash (includes Authorization token)
|
|
150
375
|
def headers
|
|
151
|
-
{
|
|
376
|
+
{"Authorization" => options[:header_format] % token}
|
|
152
377
|
end
|
|
153
378
|
|
|
154
379
|
private
|
|
155
380
|
|
|
156
|
-
def configure_authentication!(opts)
|
|
157
|
-
|
|
381
|
+
def configure_authentication!(opts, verb)
|
|
382
|
+
mode_opt = options[:mode]
|
|
383
|
+
param_name = options[:param_name]
|
|
384
|
+
mode =
|
|
385
|
+
if mode_opt.respond_to?(:call)
|
|
386
|
+
mode_opt.call(verb)
|
|
387
|
+
elsif mode_opt.is_a?(Hash)
|
|
388
|
+
key = verb.to_sym
|
|
389
|
+
# Try symbol key first, then string key; default to :header when missing
|
|
390
|
+
mode_opt[key] || mode_opt[key.to_s] || :header
|
|
391
|
+
else
|
|
392
|
+
mode_opt
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
case mode
|
|
158
396
|
when :header
|
|
159
|
-
opts[:headers] ||= {}
|
|
160
|
-
|
|
397
|
+
request_headers = opts[:headers] ||= {}
|
|
398
|
+
request_headers.merge!(headers)
|
|
161
399
|
when :query
|
|
162
|
-
|
|
163
|
-
|
|
400
|
+
# OAuth 2.1 note: Bearer tokens in the query string are omitted from the spec due to security risks.
|
|
401
|
+
# Prefer the default :header mode whenever possible.
|
|
402
|
+
request_params = opts[:params] ||= {}
|
|
403
|
+
request_params[param_name] = token
|
|
164
404
|
when :body
|
|
165
|
-
opts[:body] ||= {}
|
|
166
|
-
if
|
|
167
|
-
|
|
405
|
+
request_body = opts[:body] ||= {}
|
|
406
|
+
if request_body.is_a?(Hash)
|
|
407
|
+
request_body[param_name] = token
|
|
168
408
|
else
|
|
169
|
-
opts[:body]
|
|
409
|
+
opts[:body] = "#{request_body}&#{param_name}=#{token}"
|
|
170
410
|
end
|
|
171
411
|
# @todo support for multi-part (file uploads)
|
|
172
412
|
else
|
|
173
|
-
raise("invalid :mode option of #{
|
|
413
|
+
raise("invalid :mode option of #{mode}")
|
|
174
414
|
end
|
|
175
415
|
end
|
|
176
416
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "anonymous_loader"
|
|
4
|
+
|
|
5
|
+
module OAuth2
|
|
6
|
+
AUTH_SANITIZER = begin
|
|
7
|
+
auth_sanitizer_requirement = Gem::Requirement.new("~> 0.2", ">= 0.2.2")
|
|
8
|
+
auth_sanitizer_loader_namespace = AnonymousLoader.load_path(
|
|
9
|
+
gem_name: "auth-sanitizer",
|
|
10
|
+
require_path: "auth_sanitizer/loader.rb",
|
|
11
|
+
version_requirement: auth_sanitizer_requirement,
|
|
12
|
+
version_file: "auth/sanitizer/version.rb"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
auth_sanitizer_loader_namespace.
|
|
16
|
+
const_get(:AuthSanitizer).
|
|
17
|
+
const_get(:Loader).
|
|
18
|
+
load_isolated
|
|
19
|
+
end
|
|
20
|
+
end
|