doorkeeper 5.9.5 → 5.9.7
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
- data/CHANGELOG.md +7 -6
- data/lib/doorkeeper/config.rb +4 -0
- data/lib/doorkeeper/oauth/client/credentials.rb +19 -4
- data/lib/doorkeeper/oauth/token.rb +76 -0
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/generators/doorkeeper/templates/initializer.rb +8 -0
- metadata +2 -3
- data/lib/doorkeeper/oauth/stateless_token.rb +0 -139
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b21e3bb3454e1896aa0a0d022741de322d5cf604de408dfe58a1a4ef2a21cfbc
|
|
4
|
+
data.tar.gz: 9384048264886cad0506a879964635253fb00a4903a6347e7801c066c6f971d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18f0100ea947793a51547ee3de5ffd0927b8c4a6e1a9b491448c4149927ec34521b4d28c45056c5ca0d4b6dd87f79b796167e463f0a4cb1830eb2bf5732fc9b8
|
|
7
|
+
data.tar.gz: 16c2996fa91d47283fcbf491773d7f2ed4d0e14b8faf0566f6757875de1b31af3ac058e19a77487c74ec13ca6dfde6a619048484b73f5a4bccc9814441a24122
|
data/CHANGELOG.md
CHANGED
|
@@ -5,16 +5,18 @@ upgrade guides.
|
|
|
5
5
|
|
|
6
6
|
User-visible changes worth mentioning.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 5.9.7
|
|
9
9
|
|
|
10
|
-
-
|
|
10
|
+
- Refuse requests that transmit an access token by more than one method (RFC 6750 §2), instead of silently authorizing with the first configured `access_token_methods` entry that matched and discarding the other tokens. Such a request now fails closed as carrying no usable token (401 `invalid_token`); no calling contract changes. The form-encoded body (§2.2) and the URI query (§2.3) count as two methods even though Rails and Rack merge them into a single `params` hash. The same token repeated across two methods is refused too — §2 forbids the second method, not a disagreement between the two — and a custom callable extractor in `access_token_methods` keeps the historical first-wins behavior and is never invoked more than once. (The strict `invalid_request` (400) answer §3.1 prescribes ships with Doorkeeper 6.0.)
|
|
11
|
+
- [#1925] Internal: pin the development dependency on `json` below 3.0. json 3 removed the positional options Hash from `JSON.parse` and the `quirks_mode` option from `JSON.generate`, both of which Active Support still uses, so the suite could not run on any supported Rails version.
|
|
11
12
|
|
|
12
|
-
## 5.9.
|
|
13
|
+
## 5.9.6
|
|
13
14
|
|
|
14
|
-
-
|
|
15
|
+
- 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.
|
|
15
16
|
|
|
16
|
-
## 5.9.
|
|
17
|
+
## 5.9.5
|
|
17
18
|
|
|
19
|
+
- 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
20
|
- [#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
21
|
|
|
20
22
|
## 5.9.3
|
|
@@ -65,7 +67,6 @@ User-visible changes worth mentioning.
|
|
|
65
67
|
- [#1779] Only lock previous access token model when creating a new token from its refresh token if revoke_previous_refresh_token_on_use is false
|
|
66
68
|
- [#1778] Ensure that token revocation is idempotent by checking that that token has not already been revoked before revoking.
|
|
67
69
|
|
|
68
|
-
|
|
69
70
|
## 5.8.2
|
|
70
71
|
|
|
71
72
|
- [#1755] Fix the error message for force_pkce
|
data/lib/doorkeeper/config.rb
CHANGED
|
@@ -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,
|
|
14
|
-
#
|
|
15
|
-
# method in each
|
|
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
|
|
|
@@ -3,8 +3,54 @@
|
|
|
3
3
|
module Doorkeeper
|
|
4
4
|
module OAuth
|
|
5
5
|
class Token
|
|
6
|
+
# Built-in extractors that read a request parameter, and the parameter
|
|
7
|
+
# each of them reads. RFC 6750 treats the form-encoded body (§2.2) and
|
|
8
|
+
# the URI query string (§2.3) as two distinct transmission methods, but
|
|
9
|
+
# Rack and ActionDispatch both collapse them into a single parameter
|
|
10
|
+
# hash — ActionDispatch lets the query win, Rack lets the body win — so
|
|
11
|
+
# a request carrying the parameter in both would present a single value
|
|
12
|
+
# to the extractor and never be refused. The multi-method check reads
|
|
13
|
+
# the two sources separately for these extractors; selection keeps
|
|
14
|
+
# using the extractor, so which one wins is unchanged.
|
|
15
|
+
PARAMETER_EXTRACTORS = {
|
|
16
|
+
from_access_token_param: "access_token",
|
|
17
|
+
from_bearer_param: "bearer_token",
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
6
20
|
class << self
|
|
21
|
+
# RFC 6750 §2: "Clients MUST NOT use more than one method to transmit
|
|
22
|
+
# the token in each request", and §3.1 lists using more than one method
|
|
23
|
+
# among the conditions an invalid_request answers. Returning the first
|
|
24
|
+
# method that yields a value would discard every other token presented
|
|
25
|
+
# in the same request with no error, warning or log entry, leaving
|
|
26
|
+
# which token authorizes the request to be decided by the configured
|
|
27
|
+
# order of +access_token_methods+ rather than by what the caller sent —
|
|
28
|
+
# so a layer in front of Doorkeeper that reads a different one of them
|
|
29
|
+
# can reach a different verdict about the very same request.
|
|
30
|
+
#
|
|
31
|
+
# What §2 forbids is using more than one method, so the check counts
|
|
32
|
+
# transmission methods rather than comparing the tokens they carry:
|
|
33
|
+
# the same value presented twice is still two methods. Refusal answers
|
|
34
|
+
# nil: the caller is told the request carries no usable token and
|
|
35
|
+
# fails closed with the invalid_token (401) response, keeping every
|
|
36
|
+
# calling contract on this stable branch intact — the invalid_request
|
|
37
|
+
# (400) that §3.1 strictly prescribes needs a new error and render
|
|
38
|
+
# path, so it ships with 6.0 only.
|
|
39
|
+
#
|
|
40
|
+
# Only the built-in extractors — symbols naming methods on this class,
|
|
41
|
+
# all of them side-effect-free reads of the request — take part in
|
|
42
|
+
# that check. A custom callable extractor is a configuration adapter
|
|
43
|
+
# rather than a transmission method: it keeps the historical
|
|
44
|
+
# first-wins selection and is never invoked more than once, the same
|
|
45
|
+
# exemption client authentication gives its legacy callable
|
|
46
|
+
# extractors (Request#validate_client_authentication!).
|
|
7
47
|
def from_request(request, *methods)
|
|
48
|
+
used = methods.sum do |method|
|
|
49
|
+
method.is_a?(Symbol) ? transmission_methods_used(request, method) : 0
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
return if used > 1
|
|
53
|
+
|
|
8
54
|
methods.inject(nil) do |_, method|
|
|
9
55
|
method = self.method(method) if method.is_a?(Symbol)
|
|
10
56
|
credentials = method.call(request)
|
|
@@ -44,6 +90,36 @@ module Doorkeeper
|
|
|
44
90
|
|
|
45
91
|
private
|
|
46
92
|
|
|
93
|
+
# How many transmission methods the given built-in extractor finds a
|
|
94
|
+
# token in. Usually one, or none — but for the parameter extractors
|
|
95
|
+
# above it is the body and the query counted separately, since the
|
|
96
|
+
# parameter hash collapsed them into the single value the extractor
|
|
97
|
+
# reads. That value is counted on its own only when neither raw source
|
|
98
|
+
# explains it, so a token reaching the extractor by some other route —
|
|
99
|
+
# an :access_token path segment, or a host that overrode the extractor
|
|
100
|
+
# — is still counted exactly once rather than twice.
|
|
101
|
+
def transmission_methods_used(request, method)
|
|
102
|
+
value = self.method(method).call(request).presence
|
|
103
|
+
|
|
104
|
+
parameter = PARAMETER_EXTRACTORS[method]
|
|
105
|
+
return value ? 1 : 0 unless parameter
|
|
106
|
+
|
|
107
|
+
sources = parameter_sources(request, parameter)
|
|
108
|
+
sources.size + (value && !sources.include?(value) ? 1 : 0)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# The form-encoded body (§2.2) and the URI query (§2.3) as Rack
|
|
112
|
+
# exposes them, for a request object that keeps the two apart.
|
|
113
|
+
def parameter_sources(request, parameter)
|
|
114
|
+
return [] unless request.respond_to?(:GET) && request.respond_to?(:POST)
|
|
115
|
+
|
|
116
|
+
query = request.GET
|
|
117
|
+
body = request.POST
|
|
118
|
+
return [] unless query.is_a?(Hash) && body.is_a?(Hash)
|
|
119
|
+
|
|
120
|
+
[query[parameter], body[parameter]].filter_map(&:presence)
|
|
121
|
+
end
|
|
122
|
+
|
|
47
123
|
def token_from_basic_header(header, pattern)
|
|
48
124
|
encoded_header = token_from_header(header, pattern)
|
|
49
125
|
decode_basic_credentials_token(encoded_header)
|
data/lib/doorkeeper/version.rb
CHANGED
|
@@ -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.
|
|
4
|
+
version: 5.9.7
|
|
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-
|
|
14
|
+
date: 2026-09-10 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
|