doorkeeper 5.9.5 → 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: d3e4ee342094b7729f29bb64d9045cfd9736d99388c5bb15d68568c6d59a8a54
4
- data.tar.gz: e1549e74f00d26b85ed9afedbfaba17adce5a4e8a62c5ee01a8c4ff70c71bbcf
3
+ metadata.gz: 233efb73ba5d84b316e665251df23989888d403dd553cd34375891645c00eb14
4
+ data.tar.gz: eac0e2ca7376e065d96d43c81cafc54931f77386f2b8b2c00d1a69f7ca0f4b93
5
5
  SHA512:
6
- metadata.gz: d91e3f678a7a3d53e94c607b5b88d5a6e695e166a52fd9f5527c8988bd717c60debc292798c159da7356966baa4d26ad22d56a9b922582040e758c37ef26ea96
7
- data.tar.gz: e335f698b2a3fe29b139e826fafada70b94fd239a0c2e0f2be762075c27bb73c8dab4c93a9581089214b07f9016af893cd0c70daf0fdc2db9c8b72f7ae848850
6
+ metadata.gz: 57c525e75aee988ee335c12415b6d0cea2490890a4ae83dbccc5a9e4b16d6589792aa9c9032ccfb38dd63a30cf9d8080ec87913afe31d577cf07baa7ef916497
7
+ data.tar.gz: 6dd5a81b7fab1497885c73b601459ab6a12245dff817a17fbba3d40c39bf38a45571ef8526a09a3800baeeabd7e148ceefd412d970b5bff19df8d59374b56d74
data/CHANGELOG.md CHANGED
@@ -5,16 +5,13 @@ 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
11
 
12
12
  ## 5.9.5
13
13
 
14
- - [#1901] 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
-
16
- ## 5.9.4
17
-
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.
18
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.
19
16
 
20
17
  ## 5.9.3
@@ -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)
@@ -10,15 +10,21 @@ module Doorkeeper
10
10
  # credentials wins, as it always has.
11
11
  #
12
12
  # Raises Errors::MultipleClientAuthMethods when the request
13
- # authenticates the client with more than one method, which RFC 6749
14
- # §2.3 forbids ("The client MUST NOT use more than one authentication
15
- # method in each 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").
16
17
  def from_request(request, *credentials_methods)
17
18
  # Callable extractors are opaque: they may legitimately overlap the
18
19
  # built-in methods they are configured with, and evaluating all of
19
20
  # them would also break the existing contract that the extractors
20
21
  # after the matching one are not called. Such configurations keep
21
- # the historical behaviour untouched.
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.
22
28
  return first_from_request(request, credentials_methods) unless credentials_methods.all?(Symbol)
23
29
 
24
30
  credentials = credentials_methods.filter_map { |method| extract(request, method) }
@@ -31,6 +37,15 @@ module Doorkeeper
31
37
  # method.
32
38
  raise Errors::MultipleClientAuthMethods if credentials.count { |c| c.secret.present? } > 1
33
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
+
34
49
  credentials.first
35
50
  end
36
51
 
@@ -5,7 +5,7 @@ module Doorkeeper
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
7
  MINOR = 9
8
- TINY = 5
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.9.5
4
+ version: 5.9.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2026-08-07 00:00:00.000000000 Z
14
+ date: 2026-08-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -261,7 +261,6 @@ files:
261
261
  - lib/doorkeeper/oauth/pre_authorization.rb
262
262
  - lib/doorkeeper/oauth/refresh_token_request.rb
263
263
  - lib/doorkeeper/oauth/scopes.rb
264
- - lib/doorkeeper/oauth/stateless_token.rb
265
264
  - lib/doorkeeper/oauth/token.rb
266
265
  - lib/doorkeeper/oauth/token_introspection.rb
267
266
  - lib/doorkeeper/oauth/token_request.rb
@@ -1,139 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doorkeeper
4
- module OAuth
5
- # In-memory representation of a verified JWT access token. Returned by
6
- # OAuth::Token.authenticate when stateless_jwt_tokens is enabled, so that
7
- # the existing doorkeeper_token / doorkeeper_authorize! helpers work
8
- # without any database read.
9
- #
10
- # Stateless tokens cannot be revoked or refreshed server-side: #revoked?
11
- # is always false and #revoke is a no-op. Rely on short token lifetimes
12
- # (access_token_expires_in) for early invalidation.
13
- class StatelessToken
14
- attr_reader :claims, :application, :raw_token
15
-
16
- def initialize(claims:, application: nil, raw_token: nil)
17
- @claims = claims
18
- @application = application
19
- @raw_token = raw_token
20
- end
21
-
22
- def token_type
23
- "Bearer"
24
- end
25
-
26
- # The raw token string. Used by introspection's
27
- # authorized_token_matches_introspected? security check.
28
- def token
29
- @raw_token
30
- end
31
-
32
- # Issuance path: the freshly-signed JWT returned to the client.
33
- def plaintext_token
34
- @raw_token
35
- end
36
-
37
- def plaintext_refresh_token
38
- nil
39
- end
40
-
41
- def resource_owner_id
42
- claims["resource_owner_id"] || claims["sub"]
43
- end
44
-
45
- def application_id
46
- application&.id
47
- end
48
-
49
- def scopes_string
50
- claims["scope"].to_s
51
- end
52
-
53
- def scopes
54
- OAuth::Scopes.from_string(scopes_string)
55
- end
56
-
57
- def includes_scope?(*required_scopes)
58
- required_scopes.blank? || required_scopes.any? { |scope| scopes.exists?(scope.to_s) }
59
- end
60
-
61
- def custom_attributes
62
- claims.slice(*Doorkeeper.config.custom_access_token_attributes.map(&:to_s))
63
- end
64
-
65
- # RFC 8707 resource indicator. Stored as a space-delimited string to
66
- # match the AccessToken column shape consumed by token_introspection.
67
- def resource
68
- aud = claims["aud"]
69
- return nil if aud.blank?
70
-
71
- aud.is_a?(Array) ? aud.join(" ") : aud.to_s
72
- end
73
-
74
- def created_at
75
- Time.at(claims["iat"].to_i).utc
76
- end
77
-
78
- def expires_in
79
- claims["expires_in"]
80
- end
81
-
82
- def expires_at
83
- return nil unless claims["exp"]
84
-
85
- Time.at(claims["exp"].to_i).utc
86
- end
87
-
88
- def expires_in_seconds
89
- return nil unless expires_at
90
-
91
- expires = expires_at - Time.now.utc
92
- sec = expires.seconds.round(0)
93
- sec > 0 ? sec : 0
94
- end
95
-
96
- def expired?
97
- return false unless expires_at
98
-
99
- Time.now.utc > expires_at
100
- end
101
-
102
- def revoked?
103
- false
104
- end
105
-
106
- def accessible?
107
- !expired? && !revoked?
108
- end
109
-
110
- def acceptable?(required_scopes)
111
- accessible? && includes_scope?(*required_scopes)
112
- end
113
-
114
- def revoke_previous_refresh_token!
115
- # no-op: stateless tokens carry no refresh-token chain to revoke.
116
- end
117
-
118
- def revoke
119
- # no-op: stateless tokens cannot be revoked server-side.
120
- end
121
-
122
- def revocable?
123
- false
124
- end
125
-
126
- def as_json(_options = {})
127
- {
128
- resource_owner_id: resource_owner_id,
129
- scope: scopes,
130
- expires_in: expires_in_seconds,
131
- application: { uid: application.try(:uid) },
132
- created_at: created_at.to_i,
133
- }.tap do |json|
134
- json[:resource_owner_type] = claims["resource_owner_type"] if Doorkeeper.configuration.polymorphic_resource_owner?
135
- end
136
- end
137
- end
138
- end
139
- end