doorkeeper 5.9.3 → 5.9.5
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 +8 -0
- data/config/locales/en.yml +1 -0
- data/lib/doorkeeper/errors.rb +13 -0
- data/lib/doorkeeper/helpers/controller.rb +2 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +8 -3
- data/lib/doorkeeper/oauth/client/credentials.rb +42 -5
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +4 -1
- data/lib/doorkeeper/oauth/stateless_token.rb +139 -0
- data/lib/doorkeeper/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d3e4ee342094b7729f29bb64d9045cfd9736d99388c5bb15d68568c6d59a8a54
|
|
4
|
+
data.tar.gz: e1549e74f00d26b85ed9afedbfaba17adce5a4e8a62c5ee01a8c4ff70c71bbcf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d91e3f678a7a3d53e94c607b5b88d5a6e695e166a52fd9f5527c8988bd717c60debc292798c159da7356966baa4d26ad22d56a9b922582040e758c37ef26ea96
|
|
7
|
+
data.tar.gz: e335f698b2a3fe29b139e826fafada70b94fd239a0c2e0f2be762075c27bb73c8dab4c93a9581089214b07f9016af893cd0c70daf0fdc2db9c8b72f7ae848850
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,14 @@ User-visible changes worth mentioning.
|
|
|
9
9
|
|
|
10
10
|
- Please add here
|
|
11
11
|
|
|
12
|
+
## 5.9.5
|
|
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
|
+
|
|
18
|
+
- [#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
|
+
|
|
12
20
|
## 5.9.3
|
|
13
21
|
|
|
14
22
|
- [#1834] Fix default `allow_token_introspection` returning `false` when a custom `application_class` is configured. The default proc compared application objects with `==`, which fails when the authorized client and the introspected token's application are resolved as different classes (e.g. a base `Doorkeeper::Application` vs. a configured subclass) even though they reference the same record. It now compares application ids instead.
|
data/config/locales/en.yml
CHANGED
|
@@ -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.'
|
data/lib/doorkeeper/errors.rb
CHANGED
|
@@ -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
|
-
|
|
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,33 @@ 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, which RFC 6749
|
|
14
|
+
# §2.3 forbids ("The client MUST NOT use more than one authentication
|
|
15
|
+
# method in each request").
|
|
8
16
|
def from_request(request, *credentials_methods)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
# Callable extractors are opaque: they may legitimately overlap the
|
|
18
|
+
# built-in methods they are configured with, and evaluating all of
|
|
19
|
+
# them would also break the existing contract that the extractors
|
|
20
|
+
# after the matching one are not called. Such configurations keep
|
|
21
|
+
# the historical behaviour untouched.
|
|
22
|
+
return first_from_request(request, credentials_methods) unless credentials_methods.all?(Symbol)
|
|
23
|
+
|
|
24
|
+
credentials = credentials_methods.filter_map { |method| extract(request, method) }
|
|
25
|
+
|
|
26
|
+
# Only credentials carrying a secret authenticate the client. A bare
|
|
27
|
+
# uid is a public client identifying itself (RFC 6749 §2.3 "none"),
|
|
28
|
+
# not an authentication method of its own, so it doesn't count
|
|
29
|
+
# towards the single-method rule — RFC 7521 §4.2, for example,
|
|
30
|
+
# explicitly allows a client_id next to another authentication
|
|
31
|
+
# method.
|
|
32
|
+
raise Errors::MultipleClientAuthMethods if credentials.count { |c| c.secret.present? } > 1
|
|
33
|
+
|
|
34
|
+
credentials.first
|
|
14
35
|
end
|
|
15
36
|
|
|
16
37
|
def from_params(request)
|
|
@@ -23,6 +44,22 @@ module Doorkeeper
|
|
|
23
44
|
Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
|
|
24
45
|
end
|
|
25
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def first_from_request(request, credentials_methods)
|
|
51
|
+
credentials_methods.inject(nil) do |_, method|
|
|
52
|
+
credentials = extract(request, method)
|
|
53
|
+
break credentials if credentials
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the credentials the given method extracts from the request,
|
|
58
|
+
# or nil when it extracts none.
|
|
59
|
+
def extract(request, method)
|
|
60
|
+
method = self.method(method) if method.is_a?(Symbol)
|
|
61
|
+
Credentials.new(*method.call(request)).presence
|
|
62
|
+
end
|
|
26
63
|
end
|
|
27
64
|
|
|
28
65
|
# 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)
|
|
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
|
)
|
|
@@ -0,0 +1,139 @@
|
|
|
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
|
data/lib/doorkeeper/version.rb
CHANGED
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.
|
|
4
|
+
version: 5.9.5
|
|
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:
|
|
14
|
+
date: 2026-08-07 00:00:00.000000000 Z
|
|
14
15
|
dependencies:
|
|
15
16
|
- !ruby/object:Gem::Dependency
|
|
16
17
|
name: railties
|
|
@@ -260,6 +261,7 @@ files:
|
|
|
260
261
|
- lib/doorkeeper/oauth/pre_authorization.rb
|
|
261
262
|
- lib/doorkeeper/oauth/refresh_token_request.rb
|
|
262
263
|
- lib/doorkeeper/oauth/scopes.rb
|
|
264
|
+
- lib/doorkeeper/oauth/stateless_token.rb
|
|
263
265
|
- lib/doorkeeper/oauth/token.rb
|
|
264
266
|
- lib/doorkeeper/oauth/token_introspection.rb
|
|
265
267
|
- lib/doorkeeper/oauth/token_request.rb
|
|
@@ -329,6 +331,7 @@ metadata:
|
|
|
329
331
|
bug_tracker_uri: https://github.com/doorkeeper-gem/doorkeeper/issues
|
|
330
332
|
documentation_uri: https://doorkeeper.gitbook.io/guides/
|
|
331
333
|
funding_uri: https://opencollective.com/doorkeeper-gem
|
|
334
|
+
post_install_message:
|
|
332
335
|
rdoc_options: []
|
|
333
336
|
require_paths:
|
|
334
337
|
- lib
|
|
@@ -343,7 +346,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
343
346
|
- !ruby/object:Gem::Version
|
|
344
347
|
version: '0'
|
|
345
348
|
requirements: []
|
|
346
|
-
rubygems_version:
|
|
349
|
+
rubygems_version: 3.5.16
|
|
350
|
+
signing_key:
|
|
347
351
|
specification_version: 4
|
|
348
352
|
summary: OAuth 2 provider for Rails and Grape
|
|
349
353
|
test_files: []
|