oauth2 2.0.12 → 2.0.13

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.
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
@@ -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 i.e TLS Client Auth,
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
@@ -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
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
- # standard error codes include:
8
- # 'invalid_request', 'invalid_client', 'invalid_token', 'invalid_grant', 'unsupported_grant_type', 'invalid_scope'
9
- # response might be a Response object, or the response.parsed hash
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?
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OAuth2
4
4
  module Version
5
- VERSION = "2.0.12"
5
+ VERSION = "2.0.13"
6
6
  end
7
7
  end
data/lib/oauth2.rb CHANGED
@@ -24,22 +24,58 @@ require_relative "oauth2/access_token"
24
24
  require_relative "oauth2/response"
25
25
 
26
26
  # The namespace of this library
27
+ #
28
+ # This module is the entry point and top-level namespace for the oauth2 gem.
29
+ # It exposes configuration, constants, and requires the primary public classes.
27
30
  module OAuth2
31
+ # When true, enables verbose HTTP logging via Faraday's logger middleware.
32
+ # Controlled by the OAUTH_DEBUG environment variable. Any case-insensitive
33
+ # value equal to "true" will enable debugging.
34
+ #
35
+ # @return [Boolean]
28
36
  OAUTH_DEBUG = ENV.fetch("OAUTH_DEBUG", "false").casecmp("true").zero?
37
+
38
+ # Default configuration values for the oauth2 library.
39
+ #
40
+ # @example Toggle warnings
41
+ # OAuth2.configure do |config|
42
+ # config[:silence_extra_tokens_warning] = false
43
+ # config[:silence_no_tokens_warning] = false
44
+ # end
45
+ #
46
+ # @return [SnakyHash::SymbolKeyed] A mutable Hash-like config with symbol keys
29
47
  DEFAULT_CONFIG = SnakyHash::SymbolKeyed.new(
30
48
  silence_extra_tokens_warning: true,
31
49
  silence_no_tokens_warning: true,
32
50
  )
51
+
52
+ # The current runtime configuration for the library.
53
+ #
54
+ # @return [SnakyHash::SymbolKeyed]
33
55
  @config = DEFAULT_CONFIG.dup
56
+
34
57
  class << self
58
+ # Access the current configuration.
59
+ #
60
+ # Prefer using {OAuth2.configure} to mutate configuration.
61
+ #
62
+ # @return [SnakyHash::SymbolKeyed]
35
63
  attr_reader :config
36
64
  end
65
+
66
+ # Configure global library behavior.
67
+ #
68
+ # Yields the mutable configuration object so callers can update settings.
69
+ #
70
+ # @yieldparam [SnakyHash::SymbolKeyed] config the configuration object
71
+ # @return [void]
37
72
  def configure
38
73
  yield @config
39
74
  end
40
75
  module_function :configure
41
76
  end
42
77
 
78
+ # Extend OAuth2::Version with VersionGem helpers to provide semantic version helpers.
43
79
  OAuth2::Version.class_eval do
44
80
  extend VersionGem::Basic
45
81
  end
@@ -0,0 +1,25 @@
1
+ module OAuth2
2
+ class AccessToken
3
+ def self.from_hash: (OAuth2::Client, Hash[untyped, untyped]) -> OAuth2::AccessToken
4
+ def self.from_kvform: (OAuth2::Client, String) -> OAuth2::AccessToken
5
+
6
+ def initialize: (OAuth2::Client, String, ?Hash[Symbol, untyped]) -> void
7
+ def []: (String | Symbol) -> untyped
8
+ def expires?: () -> bool
9
+ def expired?: () -> bool
10
+ def refresh: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::AccessToken
11
+ def revoke: (?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
12
+ def to_hash: () -> Hash[Symbol, untyped]
13
+ def request: (Symbol, String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
14
+ def get: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
15
+ def post: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
16
+ def put: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
17
+ def patch: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
18
+ def delete: (String, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
19
+ def headers: () -> Hash[String, String]
20
+ def configure_authentication!: (Hash[Symbol, untyped]) -> void
21
+ def convert_expires_at: (untyped) -> (Time | Integer | nil)
22
+
23
+ attr_accessor response: OAuth2::Response
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ module OAuth2
2
+ class Authenticator
3
+ include OAuth2::FilteredAttributes
4
+
5
+ attr_reader mode: (Symbol | String)
6
+ attr_reader id: String?
7
+ attr_reader secret: String?
8
+
9
+ def initialize: (String? id, String? secret, (Symbol | String) mode) -> void
10
+
11
+ def apply: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
12
+
13
+ def self.encode_basic_auth: (String, String) -> String
14
+
15
+ private
16
+
17
+ def apply_params_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
18
+ def apply_client_id: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
19
+ def apply_basic_auth: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
20
+ def basic_auth_header: () -> Hash[String, String]
21
+ end
22
+ end
@@ -0,0 +1,52 @@
1
+ module OAuth2
2
+ class Client
3
+ RESERVED_REQ_KEYS: Array[String]
4
+ RESERVED_PARAM_KEYS: Array[String]
5
+
6
+ include OAuth2::FilteredAttributes
7
+
8
+ attr_reader id: String
9
+ attr_reader secret: String
10
+ attr_reader site: String?
11
+ attr_accessor options: Hash[Symbol, untyped]
12
+ attr_writer connection: untyped
13
+
14
+ def initialize: (String client_id, String client_secret, ?Hash[Symbol, untyped]) { (untyped) -> void } -> void
15
+
16
+ def site=: (String) -> String
17
+
18
+ def connection: () -> untyped
19
+
20
+ def authorize_url: (?Hash[untyped, untyped]) -> String
21
+ def token_url: (?Hash[untyped, untyped]) -> String
22
+ def revoke_url: (?Hash[untyped, untyped]) -> String
23
+
24
+ def request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
25
+
26
+ def get_token: (Hash[untyped, untyped] params, ?Hash[Symbol, untyped] access_token_opts, ?Proc) { (Hash[Symbol, untyped]) -> void } -> (OAuth2::AccessToken | nil)
27
+
28
+ def revoke_token: (String token, ?String token_type_hint, ?Hash[Symbol, untyped]) { (untyped) -> void } -> OAuth2::Response
29
+
30
+ def http_method: () -> Symbol
31
+
32
+ def auth_code: () -> OAuth2::Strategy::AuthCode
33
+ def implicit: () -> OAuth2::Strategy::Implicit
34
+ def password: () -> OAuth2::Strategy::Password
35
+ def client_credentials: () -> OAuth2::Strategy::ClientCredentials
36
+ def assertion: () -> OAuth2::Strategy::Assertion
37
+
38
+ def redirection_params: () -> Hash[String, String]
39
+
40
+ private
41
+
42
+ def params_to_req_opts: (Hash[untyped, untyped]) -> Hash[Symbol, untyped]
43
+ def parse_snaky_params_headers: (Hash[untyped, untyped]) -> [Symbol, bool, untyped, (Symbol | nil), Hash[untyped, untyped], Hash[String, String]]
44
+ def execute_request: (Symbol verb, String url, ?Hash[Symbol, untyped]) { (Faraday::Request) -> void } -> OAuth2::Response
45
+ def authenticator: () -> OAuth2::Authenticator
46
+ def parse_response_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
47
+ def parse_response: (OAuth2::Response, Hash[Symbol, untyped]) -> (OAuth2::AccessToken | nil)
48
+ def build_access_token: (OAuth2::Response, Hash[Symbol, untyped], untyped) -> OAuth2::AccessToken
49
+ def build_access_token_legacy: (OAuth2::Response, Hash[Symbol, untyped], Proc) -> (OAuth2::AccessToken | nil)
50
+ def oauth_debug_logging: (untyped) -> void
51
+ end
52
+ end
@@ -0,0 +1,8 @@
1
+ module OAuth2
2
+ class Error < StandardError
3
+ def initialize: (OAuth2::Response) -> void
4
+ def code: () -> (String | Integer | nil)
5
+ def description: () -> (String | nil)
6
+ def response: () -> OAuth2::Response
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ module OAuth2
2
+ module FilteredAttributes
3
+ def self.included: (untyped) -> untyped
4
+ def filtered_attributes: (*String) -> void
5
+ end
6
+ end
@@ -0,0 +1,18 @@
1
+ module OAuth2
2
+ class Response
3
+ DEFAULT_OPTIONS: Hash[Symbol, untyped]
4
+
5
+ def self.register_parser: (Symbol key, (Array[String] | String) mime_types) { (String) -> untyped } -> void
6
+
7
+ def initialize: (untyped response, parse: Symbol?, snaky: bool?, snaky_hash_klass: untyped?, options: Hash[Symbol, untyped]?) -> void
8
+ def headers: () -> Hash[untyped, untyped]
9
+ def status: () -> Integer
10
+ def body: () -> String
11
+ def parsed: () -> untyped
12
+ def content_type: () -> (String | nil)
13
+ def parser: () -> (untyped | nil)
14
+
15
+ attr_reader response: untyped
16
+ attr_accessor options: Hash[Symbol, untyped]
17
+ end
18
+ end
@@ -0,0 +1,34 @@
1
+ module OAuth2
2
+ module Strategy
3
+ class Base
4
+ def initialize: (OAuth2::Client) -> void
5
+ end
6
+
7
+ class AuthCode < Base
8
+ def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
9
+ def authorize_url: (?Hash[untyped, untyped]) -> String
10
+ def get_token: (String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
11
+ end
12
+
13
+ class Implicit < Base
14
+ def authorize_params: (?Hash[untyped, untyped]) -> Hash[untyped, untyped]
15
+ def authorize_url: (?Hash[untyped, untyped]) -> String
16
+ def get_token: (*untyped) -> void
17
+ end
18
+
19
+ class Password < Base
20
+ def authorize_url: () -> void
21
+ def get_token: (String, String, ?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
22
+ end
23
+
24
+ class ClientCredentials < Base
25
+ def authorize_url: () -> void
26
+ def get_token: (?Hash[untyped, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
27
+ end
28
+
29
+ class Assertion < Base
30
+ def authorize_url: () -> void
31
+ def get_token: (Hash[untyped, untyped], Hash[Symbol, untyped], ?Hash[Symbol, untyped], ?Hash[Symbol, untyped]) -> OAuth2::AccessToken
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module OAuth2
2
+ module Version
3
+ VERSION: String
4
+ end
5
+ end
data/sig/oauth2.rbs ADDED
@@ -0,0 +1,9 @@
1
+ module OAuth2
2
+ OAUTH_DEBUG: bool
3
+
4
+ DEFAULT_CONFIG: untyped
5
+ @config: untyped
6
+
7
+ def self.config: () -> untyped
8
+ def self.configure: () { (untyped) -> void } -> void
9
+ end
data.tar.gz.sig CHANGED
Binary file