doorkeeper 5.9.3 → 5.9.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71a32b8766b44f5eff16fbc128cfff6ab676b5af9104d16d58f060a0ed6d894c
4
- data.tar.gz: 864e07764f1ba86d6f52a10e5a081291c2748dcecbfb8bcc60b0f27baa4e616e
3
+ metadata.gz: 233efb73ba5d84b316e665251df23989888d403dd553cd34375891645c00eb14
4
+ data.tar.gz: eac0e2ca7376e065d96d43c81cafc54931f77386f2b8b2c00d1a69f7ca0f4b93
5
5
  SHA512:
6
- metadata.gz: 4116cfa7e4e06a1bff79489ddeced66af1803dedd6f2e1b1b5edd67db0d90e444922a87cfc2215a4e61357e54ddf988606c3ecce40e50afb24fdd5e06808a2bd
7
- data.tar.gz: 558ec2ac88215ba2cde034380a27d44d0c989841942c070e4ea02a746d154944b094ae2aa6a9284c7cca6d310705634fde4f6566fbf8a268a0c1b33e41937107
6
+ metadata.gz: 57c525e75aee988ee335c12415b6d0cea2490890a4ae83dbccc5a9e4b16d6589792aa9c9032ccfb38dd63a30cf9d8080ec87913afe31d577cf07baa7ef916497
7
+ data.tar.gz: 6dd5a81b7fab1497885c73b601459ab6a12245dff817a17fbba3d40c39bf38a45571ef8526a09a3800baeeabd7e148ceefd412d970b5bff19df8d59374b56d74
data/CHANGELOG.md CHANGED
@@ -5,9 +5,14 @@ upgrade guides.
5
5
 
6
6
  User-visible changes worth mentioning.
7
7
 
8
- ## main
8
+ ## 5.9.6
9
9
 
10
- - Please add here
10
+ - Reject requests that present more than one client identity (e.g. an `Authorization: Basic` header for one client and a `client_id` parameter naming another) with an `invalid_request` error, instead of authenticating the first extracted identity and silently discarding the other one. A `client_id` sent alongside another authentication method keeps working when it identifies the same client (RFC 7521 §4.2). Like the RFC 6749 §2.3 check released in 5.9.5, this validation does not apply when `client_credentials` is configured with a callable extractor, since the credentials the remaining extractors would return are never evaluated — the `client_credentials` option documents that now.
11
+
12
+ ## 5.9.5
13
+
14
+ - Reject requests that authenticate the client with more than one method (RFC 6749 §2.3) with an `invalid_request` error, instead of silently authenticating with the first method that matched and discarding the other credentials.
15
+ - [#1853] Fix `reuse_access_token` reusing a token that was created with `custom_access_token_attributes` values when the new request doesn't specify any custom attributes. Such requests now only match tokens without custom attributes.
11
16
 
12
17
  ## 5.9.3
13
18
 
@@ -97,6 +97,7 @@ en:
97
97
  missing_param: 'Missing required parameter: %{value}.'
98
98
  request_not_authorized: 'Request needs to be authorized. Required parameter for authorizing the request is missing or invalid.'
99
99
  invalid_code_challenge: 'Code challenge is required.'
100
+ multiple_client_auth_methods: 'The request utilizes more than one mechanism for authenticating the client.'
100
101
  invalid_redirect_uri: "The requested redirect URI is malformed or doesn't match the client redirect URI."
101
102
  unauthorized_client: 'The client is not authorized to perform this request using this method.'
102
103
  access_denied: 'The resource owner or authorization server denied the request.'
@@ -69,6 +69,10 @@ module Doorkeeper
69
69
  # falls back to the `:client_id` and `:client_secret` params from the
70
70
  # `params` object.
71
71
  #
72
+ # NOTE: a list that includes a callable extractor opts out of the RFC 6749
73
+ # §2.3 validation applied to the symbol extractors — see
74
+ # +Doorkeeper::OAuth::Client::Credentials.from_request+.
75
+ #
72
76
  # @param methods [Array] Define client credentials
73
77
  def client_credentials(*methods)
74
78
  @config.instance_variable_set(:@client_credentials_methods, methods)
@@ -24,6 +24,19 @@ module Doorkeeper
24
24
  end
25
25
  end
26
26
 
27
+ # Raised when a request uses more than one client authentication method,
28
+ # which RFC 6749 §2.3 explicitly forbids ("The client MUST NOT use more
29
+ # than one authentication method in each request").
30
+ class MultipleClientAuthMethods < DoorkeeperError
31
+ def type
32
+ :invalid_request
33
+ end
34
+
35
+ def reason
36
+ :multiple_client_auth_methods
37
+ end
38
+ end
39
+
27
40
  class MissingRequiredParameter < DoorkeeperError
28
41
  attr_reader :missing_param
29
42
 
@@ -56,7 +56,8 @@ module Doorkeeper
56
56
  OAuth::InvalidRequestResponse.new(
57
57
  name: exception.type,
58
58
  state: params[:state],
59
- missing_param: exception.missing_param,
59
+ missing_param: exception.try(:missing_param),
60
+ reason: exception.try(:reason),
60
61
  )
61
62
  else
62
63
  OAuth::ErrorResponse.new(name: exception.type, state: params[:state])
@@ -89,7 +89,8 @@ module Doorkeeper
89
89
  # @param custom_attributes [Nilable Hash]
90
90
  # A nil value, or hash with keys corresponding to the custom attributes
91
91
  # configured with the `custom_access_token_attributes` config option.
92
- # A nil value will ignore custom attributes.
92
+ # A nil value will ignore custom attributes, while an empty hash will
93
+ # only match tokens that have no custom attributes set.
93
94
  #
94
95
  # @return [Doorkeeper::AccessToken, nil] Access Token instance or
95
96
  # nil if matching record was not found
@@ -124,7 +125,8 @@ module Doorkeeper
124
125
  # @param custom_attributes [Nilable Hash]
125
126
  # A nil value, or hash with keys corresponding to the custom attributes
126
127
  # configured with the `custom_access_token_attributes` config option.
127
- # A nil value will ignore custom attributes.
128
+ # A nil value will ignore custom attributes, while an empty hash will
129
+ # only match tokens that have no custom attributes set.
128
130
  #
129
131
  # @return [Doorkeeper::AccessToken, nil] Access Token instance or
130
132
  # nil if matching record was not found
@@ -220,7 +222,10 @@ module Doorkeeper
220
222
  scopes = Doorkeeper::OAuth::Scopes.from_string(scopes) if scopes.is_a?(String)
221
223
 
222
224
  if Doorkeeper.config.reuse_access_token
223
- custom_attributes = extract_custom_attributes(token_attributes).presence
225
+ # An empty hash must stay distinct from nil here: nil ignores custom
226
+ # attributes when matching, while an empty hash only matches tokens
227
+ # that have no custom attributes set.
228
+ custom_attributes = extract_custom_attributes(token_attributes)
224
229
  access_token = matching_token_for(
225
230
  application, resource_owner, scopes, custom_attributes: custom_attributes, include_expired: false,
226
231
  )
@@ -5,12 +5,48 @@ module Doorkeeper
5
5
  class Client
6
6
  Credentials = Struct.new(:uid, :secret) do
7
7
  class << self
8
+ # Extracts the client credentials from the request using the
9
+ # configured extraction methods. The first method that yields
10
+ # credentials wins, as it always has.
11
+ #
12
+ # Raises Errors::MultipleClientAuthMethods when the request
13
+ # authenticates the client with more than one method, or presents
14
+ # more than one client identity, which RFC 6749 §2.3 forbids ("The
15
+ # client MUST NOT use more than one authentication method in each
16
+ # request").
8
17
  def from_request(request, *credentials_methods)
9
- credentials_methods.inject(nil) do |_, method|
10
- method = self.method(method) if method.is_a?(Symbol)
11
- credentials = Credentials.new(*method.call(request))
12
- break credentials if credentials.present?
13
- end
18
+ # Callable extractors are opaque: they may legitimately overlap the
19
+ # built-in methods they are configured with, and evaluating all of
20
+ # them would also break the existing contract that the extractors
21
+ # after the matching one are not called. Such configurations keep
22
+ # the historical behaviour untouched, including the fact that a
23
+ # request authenticating with more than one method, or presenting
24
+ # more than one client identity, resolves to the first extracted
25
+ # credentials instead of being rejected: the extractor that would
26
+ # have surfaced the second identity is never evaluated. The
27
+ # +client_credentials+ configuration option documents this.
28
+ return first_from_request(request, credentials_methods) unless credentials_methods.all?(Symbol)
29
+
30
+ credentials = credentials_methods.filter_map { |method| extract(request, method) }
31
+
32
+ # Only credentials carrying a secret authenticate the client. A bare
33
+ # uid is a public client identifying itself (RFC 6749 §2.3 "none"),
34
+ # not an authentication method of its own, so it doesn't count
35
+ # towards the single-method rule — RFC 7521 §4.2, for example,
36
+ # explicitly allows a client_id next to another authentication
37
+ # method.
38
+ raise Errors::MultipleClientAuthMethods if credentials.count { |c| c.secret.present? } > 1
39
+
40
+ # §4.2 allows that bare client_id because it identifies the *same*
41
+ # client as the authentication method it accompanies. Credentials
42
+ # naming two different clients are two client identities in one
43
+ # request and authenticate neither: without this check the first
44
+ # extracted uid would win and the other identity would be silently
45
+ # discarded, so the caller could authenticate as one client while
46
+ # the request asks to act as another.
47
+ raise Errors::MultipleClientAuthMethods if credentials.uniq(&:uid).size > 1
48
+
49
+ credentials.first
14
50
  end
15
51
 
16
52
  def from_params(request)
@@ -23,6 +59,22 @@ module Doorkeeper
23
59
  Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
24
60
  end
25
61
  end
62
+
63
+ private
64
+
65
+ def first_from_request(request, credentials_methods)
66
+ credentials_methods.inject(nil) do |_, method|
67
+ credentials = extract(request, method)
68
+ break credentials if credentials
69
+ end
70
+ end
71
+
72
+ # Returns the credentials the given method extracts from the request,
73
+ # or nil when it extracts none.
74
+ def extract(request, method)
75
+ method = self.method(method) if method.is_a?(Symbol)
76
+ Credentials.new(*method.call(request)).presence
77
+ end
26
78
  end
27
79
 
28
80
  # Public clients may have their secret blank, but "credentials" are
@@ -45,8 +45,11 @@ module Doorkeeper
45
45
  end
46
46
 
47
47
  def find_active_existing_token_for(client, scopes, attributes)
48
+ # An empty hash must stay distinct from nil here: nil ignores custom
49
+ # attributes when matching, while an empty hash only matches tokens
50
+ # that have no custom attributes set.
48
51
  custom_attributes = Doorkeeper.config.access_token_model
49
- .extract_custom_attributes(attributes).presence
52
+ .extract_custom_attributes(attributes)
50
53
  Doorkeeper.config.access_token_model.matching_token_for(
51
54
  client, nil, scopes, custom_attributes: custom_attributes, include_expired: false,
52
55
  )
@@ -5,7 +5,7 @@ module Doorkeeper
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
7
  MINOR = 9
8
- TINY = 3
8
+ TINY = 6
9
9
  PRE = nil
10
10
 
11
11
  # Full version number
@@ -283,6 +283,14 @@ Doorkeeper.configure do
283
283
  # Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated
284
284
  # for more information on customization
285
285
  #
286
+ # NOTE: configuring a callable extractor opts the request out of the RFC 6749
287
+ # §2.3 checks that reject a request authenticating the client with more than
288
+ # one method, or presenting more than one client identity. A callable may
289
+ # legitimately overlap the built-in extractors, and the extractors after the
290
+ # matching one are never called, so what they would have returned is unknown.
291
+ # Prefer the symbol extractors unless you need something they cannot express;
292
+ # a callable has to reject such a request itself.
293
+ #
286
294
  # client_credentials :from_basic, :from_params
287
295
 
288
296
  # Change the way access token is authenticated from the request object.
metadata CHANGED
@@ -1,16 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.9.3
4
+ version: 5.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
8
8
  - Tute Costa
9
9
  - Jon Moss
10
10
  - Nikita Bulai
11
+ autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
13
- date: 1980-01-02 00:00:00.000000000 Z
14
+ date: 2026-08-11 00:00:00.000000000 Z
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: railties
@@ -329,6 +330,7 @@ metadata:
329
330
  bug_tracker_uri: https://github.com/doorkeeper-gem/doorkeeper/issues
330
331
  documentation_uri: https://doorkeeper.gitbook.io/guides/
331
332
  funding_uri: https://opencollective.com/doorkeeper-gem
333
+ post_install_message:
332
334
  rdoc_options: []
333
335
  require_paths:
334
336
  - lib
@@ -343,7 +345,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
343
345
  - !ruby/object:Gem::Version
344
346
  version: '0'
345
347
  requirements: []
346
- rubygems_version: 4.0.11
348
+ rubygems_version: 3.5.16
349
+ signing_key:
347
350
  specification_version: 4
348
351
  summary: OAuth 2 provider for Rails and Grape
349
352
  test_files: []