Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 28 additions & 27 deletions .rubocop_gradual.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
[66, 5, 20, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 2485198147],
[78, 5, 74, "Style/InvertibleUnlessCondition: Prefer `if Gem.rubygems_version >= Gem::Version.new(\"2.7.0\")` over `unless Gem.rubygems_version < Gem::Version.new(\"2.7.0\")`.", 2453573257]
],
"lib/oauth2.rb:1956148869": [
[35, 5, 21, "ThreadSafety/ClassAndModuleAttributes: Avoid mutating class and module attributes.", 622027168],
"lib/oauth2.rb:4176768025": [
[38, 11, 7, "ThreadSafety/ClassInstanceVariable: Avoid class instance variables.", 651502127]
],
"lib/oauth2/access_token.rb:2233632404": [
"lib/oauth2/access_token.rb:3471244990": [
[49, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513],
[55, 13, 5, "Style/IdenticalConditionalBranches: Move `t_key` out of the conditional.", 183811513]
],
"lib/oauth2/authenticator.rb:3711266135": [
"lib/oauth2/authenticator.rb:63639854": [
[42, 5, 113, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 734523108]
],
"lib/oauth2/filtered_attributes.rb:1202323815": [
Expand All @@ -32,11 +31,15 @@
[130, 3, 52, "Gemspec/DependencyVersion: Dependency version specification is required.", 3163430777],
[131, 3, 48, "Gemspec/DependencyVersion: Dependency version specification is required.", 425065368]
],
"spec/oauth2/access_token_spec.rb:3473606468": [
"spec/oauth2/access_token_spec.rb:443932125": [
[3, 1, 34, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/access_token*_spec.rb`.", 1972107547],
[780, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
[850, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
[854, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
[392, 142, 40, "Lint/LiteralInInterpolation: Literal interpolation detected.", 4210228387],
[400, 142, 40, "Lint/LiteralInInterpolation: Literal interpolation detected.", 4210228387],
[606, 142, 20, "Lint/LiteralInInterpolation: Literal interpolation detected.", 304063511],
[632, 142, 20, "Lint/LiteralInInterpolation: Literal interpolation detected.", 304063511],
[781, 13, 25, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 770233088],
[851, 9, 101, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3022740639],
[855, 9, 79, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 2507338967]
],
"spec/oauth2/authenticator_spec.rb:853320290": [
[3, 1, 36, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/authenticator*_spec.rb`.", 819808017],
Expand All @@ -45,26 +48,24 @@
[69, 15, 38, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1480816240],
[79, 13, 23, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2314399065]
],
"spec/oauth2/client_spec.rb:2085440011": [
"spec/oauth2/client_spec.rb:1326196445": [
[6, 1, 29, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/client*_spec.rb`.", 439549885],
[174, 7, 492, "RSpec/NoExpectationExample: No expectation found in this example.", 1272021224],
[193, 7, 592, "RSpec/NoExpectationExample: No expectation found in this example.", 3428877205],
[206, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2320605227],
[221, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1276531672],
[236, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1383956904],
[251, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3376202107],
[829, 5, 360, "RSpec/NoExpectationExample: No expectation found in this example.", 536201463],
[838, 5, 461, "RSpec/NoExpectationExample: No expectation found in this example.", 3392600621],
[849, 5, 340, "RSpec/NoExpectationExample: No expectation found in this example.", 244592251],
[894, 63, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785],
[939, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
[943, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
[951, 7, 89, "RSpec/NoExpectationExample: No expectation found in this example.", 4609419],
[1039, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
[1043, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
[1123, 17, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 664794325],
[1148, 5, 459, "RSpec/NoExpectationExample: No expectation found in this example.", 2216851076],
[1158, 7, 450, "RSpec/NoExpectationExample: No expectation found in this example.", 2619808549]
[175, 7, 492, "RSpec/NoExpectationExample: No expectation found in this example.", 1272021224],
[194, 7, 592, "RSpec/NoExpectationExample: No expectation found in this example.", 3428877205],
[207, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 2320605227],
[222, 15, 20, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1276531672],
[237, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 1383956904],
[252, 15, 43, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 3376202107],
[830, 5, 360, "RSpec/NoExpectationExample: No expectation found in this example.", 536201463],
[839, 5, 461, "RSpec/NoExpectationExample: No expectation found in this example.", 3392600621],
[850, 5, 340, "RSpec/NoExpectationExample: No expectation found in this example.", 244592251],
[895, 63, 2, "RSpec/BeEq: Prefer `be` over `eq`.", 5860785],
[940, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
[944, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
[952, 7, 89, "RSpec/NoExpectationExample: No expectation found in this example.", 4609419],
[1040, 11, 99, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 3084776886],
[1044, 11, 82, "Style/ClassMethodsDefinitions: Use `class << self` to define a class method.", 1524553529],
[1124, 17, 12, "RSpec/ContextWording: Context description should match /^when\\b/, /^with\\b/, or /^without\\b/.", 664794325]
],
"spec/oauth2/error_spec.rb:1209122273": [
[23, 1, 28, "RSpec/SpecFilePathFormat: Spec path should end with `o_auth2/error*_spec.rb`.", 3385870076],
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning v2](https://semver.org/spec/v2.
- Specify the parameter name that identifies the access token
- [!645](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/645) - Add `OAuth2::OAUTH_DEBUG` constant, based on `ENV["OAUTH_DEBUG"] (@pboling)
- [!646](https://gitlab.com/oauth-xx/oauth2/-/merge_requests/646) - Add `OAuth2.config.silence_extra_tokens_warning`, default: false (@pboling)
- Added IETF RFC 7009 Token Revocation compliant `OAuth2::Client#revoke_token` and `OAuth2::AccessToken#revoke`
- See: https://datatracker.ietf.org/doc/html/rfc7009
### Changed
- Default value of `OAuth2.config.silence_extra_tokens_warning` was `false`, now `true`
- Gem releases are now cryptographically signed, with a 20-year cert (@pboling)
Expand Down
2 changes: 1 addition & 1 deletion lib/oauth2.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module OAuth2
)
@config = DEFAULT_CONFIG.dup
class << self
attr_accessor :config
attr_reader :config
end
def configure
yield @config
Expand Down
104 changes: 91 additions & 13 deletions lib/oauth2/access_token.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class << self
# @note If no token keys are present, a warning will be issued unless
# OAuth2.config.silence_no_tokens_warning is true
# @note For "soon-to-expire"/"clock-skew" functionality see the `:expires_latency` option.
# @mote If snaky key conversion is being used, token_name needs to match the converted key.
# @note If snaky key conversion is being used, token_name needs to match the converted key.
#
# @example
# hash = { 'access_token' => 'token_value', 'refresh_token' => 'refresh_value' }
Expand Down Expand Up @@ -125,8 +125,10 @@ def initialize(client, token, opts = {})
no_tokens = (@token.nil? || @token.empty?) && (@refresh_token.nil? || @refresh_token.empty?)
if no_tokens
if @client.options[:raise_errors]
error = Error.new(opts)
raise(error)
raise Error.new({
error: "OAuth2::AccessToken has no token",
error_description: "Options are: #{opts.inspect}",
})
elsif !OAuth2.config.silence_no_tokens_warning
warn("OAuth2::AccessToken has no token")
end
Expand Down Expand Up @@ -155,33 +157,40 @@ def [](key)
@params[key]
end

# Whether or not the token expires
# Whether the token expires
#
# @return [Boolean]
def expires?
!!@expires_at
end

# Whether or not the token is expired
# Check if token is expired
#
# @return [Boolean]
# @return [Boolean] true if the token is expired, false otherwise
def expired?
expires? && (expires_at <= Time.now.to_i)
end

# Refreshes the current Access Token
#
# @return [AccessToken] a new AccessToken
# @note options should be carried over to the new AccessToken
def refresh(params = {}, access_token_opts = {})
raise("A refresh_token is not available") unless refresh_token
# @param [Hash] params additional params to pass to the refresh token request
# @param [Hash] access_token_opts options that will be passed to the AccessToken initialization
#
# @yield [opts] The block to modify the refresh token request options
# @yieldparam [Hash] opts The options hash that can be modified
#
# @return [OAuth2::AccessToken] a new AccessToken instance
#
# @note current token's options are carried over to the new AccessToken
def refresh(params = {}, access_token_opts = {}, &block)
raise OAuth2::Error.new({error: "A refresh_token is not available"}) unless refresh_token

params[:grant_type] = "refresh_token"
params[:refresh_token] = refresh_token
new_token = @client.get_token(params, access_token_opts)
new_token = @client.get_token(params, access_token_opts, &block)
new_token.options = options
if new_token.refresh_token
# Keep it, if there is one
# Keep it if there is one
else
new_token.refresh_token = refresh_token
end
Expand All @@ -191,6 +200,66 @@ def refresh(params = {}, access_token_opts = {})
# @note does not modify the receiver, so bang is not the default method
alias_method :refresh!, :refresh

# Revokes the token at the authorization server
#
# @param [Hash] params additional parameters to be sent during revocation
# @option params [String, Symbol, nil] :token_type_hint ('access_token' or 'refresh_token') hint about which token to revoke
# @option params [Symbol] :token_method (:post_with_query_string) overrides OAuth2::Client#options[:token_method]
#
# @yield [req] The block is passed the request being made, allowing customization
# @yieldparam [Faraday::Request] req The request object that can be modified
#
# @return [OAuth2::Response] OAuth2::Response instance
#
# @api public
#
# @raise [OAuth2::Error] if token_type_hint is invalid or the specified token is not available
#
# @note If the token passed to the request
# is an access token, the server MAY revoke the respective refresh
# token as well.
# @note If the token passed to the request
# is a refresh token and the authorization server supports the
# revocation of access tokens, then the authorization server SHOULD
# also invalidate all access tokens based on the same authorization
# grant
# @note If the server responds with HTTP status code 503, your code must
# assume the token still exists and may retry after a reasonable delay.
# The server may include a "Retry-After" header in the response to
# indicate how long the service is expected to be unavailable to the
# requesting client.
#
# @see https://datatracker.ietf.org/doc/html/rfc7009
# @see https://datatracker.ietf.org/doc/html/rfc7009#section-2.1
def revoke(params = {}, &block)
token_type_hint_orig = params.delete(:token_type_hint)
token_type_hint = nil
revoke_token = case token_type_hint_orig
when "access_token", :access_token
token_type_hint = "access_token"
token
when "refresh_token", :refresh_token
token_type_hint = "refresh_token"
refresh_token
when nil
if token
token_type_hint = "access_token"
token
elsif refresh_token
token_type_hint = "refresh_token"
refresh_token
end
else
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."})
end
raise OAuth2::Error.new({error: "#{token_type_hint || "unknown token type"} is not available for revoking"}) unless revoke_token && !revoke_token.empty?

@client.revoke_token(revoke_token, token_type_hint, params, &block)
end
# A compatibility alias
# @note does not modify the receiver, so bang is not the default method
alias_method :revoke!, :revoke

# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
#
# @note Don't return expires_latency because it has already been deducted from expires_at
Expand Down Expand Up @@ -220,7 +289,16 @@ def to_hash
# @param [Symbol] verb the HTTP request method
# @param [String] path the HTTP URL path of the request
# @param [Hash] opts the options to make the request with
# @see Client#request
# @option opts [Hash] :params additional URL parameters
# @option opts [Hash, String] :body the request body
# @option opts [Hash] :headers request headers
#
# @yield [req] The block to modify the request
# @yieldparam [Faraday::Request] req The request object that can be modified
#
# @return [OAuth2::Response] the response from the request
#
# @see OAuth2::Client#request
def request(verb, path, opts = {}, &block)
configure_authentication!(opts)
@client.request(verb, path, opts, &block)
Expand Down
2 changes: 1 addition & 1 deletion lib/oauth2/authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(id, secret, mode)

# Apply the request credentials used to authenticate to the Authorization Server
#
# Depending on configuration, this might be as request params or as an
# Depending on the configuration, this might be as request params or as an
# Authorization header.
#
# User-provided params and header take precedence.
Expand Down
Loading
Loading