doorkeeper 5.6.9 → 5.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -4
- data/app/controllers/doorkeeper/authorizations_controller.rb +9 -3
- data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
- data/app/views/doorkeeper/authorizations/form_post.html.erb +1 -1
- data/lib/doorkeeper/config.rb +21 -0
- data/lib/doorkeeper/errors.rb +1 -0
- data/lib/doorkeeper/models/access_token_mixin.rb +61 -5
- data/lib/doorkeeper/oauth/authorization_code_request.rb +14 -0
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +6 -3
- data/lib/doorkeeper/oauth/pre_authorization.rb +8 -1
- data/lib/doorkeeper/oauth/token_response.rb +3 -1
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/generators/doorkeeper/templates/initializer.rb +16 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df8ee24bf06e6b24c9ee822c24abf45ce0424b93ff05361dcdff76c930fa3c5a
|
4
|
+
data.tar.gz: 56e84b30480a60d02eea4b417f41c1cd6b322365bfce0e9fa31aad504def3807
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d25945505890cb67e1e2db1e0a7eb8d49cd8bcccf05d2732883df6ace7269fe4bbe3de491a563ea3e254e1ac16e2daa407f665078cf243f7131a26db00b39842
|
7
|
+
data.tar.gz: 2269f220720be56f31928a1ab4180254058bc2b636994160db72030bc32f0a5db695e76b3186f6b0c24b8d77565bd4c49911d4f5a632b14c6bd57e213c278694
|
data/CHANGELOG.md
CHANGED
@@ -7,7 +7,19 @@ User-visible changes worth mentioning.
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
-
|
10
|
+
Add your entry here.
|
11
|
+
|
12
|
+
## 5.7.1
|
13
|
+
|
14
|
+
- [#1705] Add `force_pkce` option that requires non-confidential clients to use PKCE when requesting an access_token using an authorization code
|
15
|
+
|
16
|
+
## 5.7.0
|
17
|
+
|
18
|
+
- [#1696] Add missing `#issued_token` method to `OAuth::TokenResponse`
|
19
|
+
- [#1697] Allow a TokenResponse body to be customized (memoize response body).
|
20
|
+
- [#1702] Fix bugs for error response in the form_post and error view
|
21
|
+
- [#1660] Custom access token attributes are now considered when finding matching tokens (fixes #1665).
|
22
|
+
Introduce `revoke_previous_client_credentials_token` configuration option.
|
11
23
|
|
12
24
|
## 5.6.9
|
13
25
|
|
@@ -33,17 +45,17 @@ User-visible changes worth mentioning.
|
|
33
45
|
- [#1648] Add custom token attributes to Refresh Token Request.
|
34
46
|
- [#1649] Fixed custom_access_token_attributes related errors.
|
35
47
|
|
36
|
-
|
48
|
+
## 5.6.5
|
37
49
|
|
38
50
|
- [#1602] Allow custom data to be stored inside access grants/tokens.
|
39
51
|
- [#1634] Code refactoring for custom token attributes.
|
40
52
|
- [#1639] Add grant type validation to avoid Internal Server Error for DELETE /oauth/authorize endpoint.
|
41
53
|
|
42
|
-
|
54
|
+
## 5.6.4
|
43
55
|
|
44
56
|
- [#1633] Apply ORM configuration in #to_prepare block to avoid autoloading errors.
|
45
57
|
|
46
|
-
|
58
|
+
## 5.6.3
|
47
59
|
|
48
60
|
- [#1622] Drop support for Rubies 2.5 and 2.6
|
49
61
|
- [#1605] Fix URI validation for Ruby 3.2+.
|
@@ -31,7 +31,7 @@ module Doorkeeper
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def render_success
|
34
|
-
if skip_authorization? ||
|
34
|
+
if skip_authorization? || can_authorize_response?
|
35
35
|
redirect_or_render(authorize_response)
|
36
36
|
elsif Doorkeeper.configuration.api_only
|
37
37
|
render json: pre_auth
|
@@ -52,10 +52,16 @@ module Doorkeeper
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
def can_authorize_response?
|
56
|
+
Doorkeeper.config.custom_access_token_attributes.empty? && pre_auth.client.application.confidential? && matching_token?
|
57
|
+
end
|
58
|
+
|
55
59
|
# Active access token issued for the same client and resource owner with
|
56
60
|
# the same set of the scopes exists?
|
57
61
|
def matching_token?
|
58
|
-
|
62
|
+
# We don't match tokens on the custom attributes here - we're in the pre-auth here,
|
63
|
+
# so they haven't been supplied yet (there are no custom attributes to match on yet)
|
64
|
+
@matching_token ||= Doorkeeper.config.access_token_model.matching_token_for(
|
59
65
|
pre_auth.client,
|
60
66
|
current_resource_owner,
|
61
67
|
pre_auth.scopes,
|
@@ -77,7 +83,7 @@ module Doorkeeper
|
|
77
83
|
)
|
78
84
|
end
|
79
85
|
elsif pre_auth.form_post_response?
|
80
|
-
render :form_post
|
86
|
+
render :form_post, locals: { auth: auth }
|
81
87
|
else
|
82
88
|
redirect_to auth.redirect_uri, allow_other_host: true
|
83
89
|
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
</header>
|
4
4
|
|
5
5
|
<%= form_tag @pre_auth.redirect_uri, method: :post, name: :redirect_form, authenticity_token: false do %>
|
6
|
-
<%
|
6
|
+
<% auth.body.compact.each do |key, value| %>
|
7
7
|
<%= hidden_field_tag key, value %>
|
8
8
|
<% end %>
|
9
9
|
<% end %>
|
data/lib/doorkeeper/config.rb
CHANGED
@@ -106,6 +106,19 @@ module Doorkeeper
|
|
106
106
|
@config.instance_variable_set(:@revoke_previous_client_credentials_token, true)
|
107
107
|
end
|
108
108
|
|
109
|
+
# Only allow one valid access token obtained via authorization code
|
110
|
+
# per client. If a new access token is obtained before the old one
|
111
|
+
# expired, the old one gets revoked (disabled by default)
|
112
|
+
def revoke_previous_authorization_code_token
|
113
|
+
@config.instance_variable_set(:@revoke_previous_authorization_code_token, true)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Require non-confidential apps to use PKCE (send a code_verifier) when requesting
|
117
|
+
# an access_token using an authorization code (disabled by default)
|
118
|
+
def force_pkce
|
119
|
+
@config.instance_variable_set(:@force_pkce, true)
|
120
|
+
end
|
121
|
+
|
109
122
|
# Use an API mode for applications generated with --api argument
|
110
123
|
# It will skip applications controller, disable forgery protection
|
111
124
|
def api_only
|
@@ -481,6 +494,14 @@ module Doorkeeper
|
|
481
494
|
option_set? :revoke_previous_client_credentials_token
|
482
495
|
end
|
483
496
|
|
497
|
+
def revoke_previous_authorization_code_token?
|
498
|
+
option_set? :revoke_previous_authorization_code_token
|
499
|
+
end
|
500
|
+
|
501
|
+
def force_pkce?
|
502
|
+
option_set? :force_pkce
|
503
|
+
end
|
504
|
+
|
484
505
|
def enforce_configured_scopes?
|
485
506
|
option_set? :enforce_configured_scopes
|
486
507
|
end
|
data/lib/doorkeeper/errors.rb
CHANGED
@@ -54,6 +54,7 @@ module Doorkeeper
|
|
54
54
|
InvalidClient = Class.new(BaseResponseError)
|
55
55
|
InvalidScope = Class.new(BaseResponseError)
|
56
56
|
InvalidRedirectUri = Class.new(BaseResponseError)
|
57
|
+
InvalidCodeChallenge = Class.new(BaseResponseError)
|
57
58
|
InvalidCodeChallengeMethod = Class.new(BaseResponseError)
|
58
59
|
InvalidGrant = Class.new(BaseResponseError)
|
59
60
|
|
@@ -83,14 +83,18 @@ module Doorkeeper
|
|
83
83
|
# Resource Owner model instance or it's ID
|
84
84
|
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
85
85
|
# set of scopes
|
86
|
+
# @param custom_attributes [Nilable Hash]
|
87
|
+
# A nil value, or hash with keys corresponding to the custom attributes
|
88
|
+
# configured with the `custom_access_token_attributes` config option.
|
89
|
+
# A nil value will ignore custom attributes.
|
86
90
|
#
|
87
91
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
88
92
|
# nil if matching record was not found
|
89
93
|
#
|
90
|
-
def matching_token_for(application, resource_owner, scopes, include_expired: true)
|
94
|
+
def matching_token_for(application, resource_owner, scopes, custom_attributes: nil, include_expired: true)
|
91
95
|
tokens = authorized_tokens_for(application&.id, resource_owner)
|
92
96
|
tokens = tokens.not_expired unless include_expired
|
93
|
-
find_matching_token(tokens, application, scopes)
|
97
|
+
find_matching_token(tokens, application, custom_attributes, scopes)
|
94
98
|
end
|
95
99
|
|
96
100
|
# Interface to enumerate access token records in batches in order not
|
@@ -114,11 +118,15 @@ module Doorkeeper
|
|
114
118
|
# Application instance
|
115
119
|
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
116
120
|
# set of scopes
|
121
|
+
# @param custom_attributes [Nilable Hash]
|
122
|
+
# A nil value, or hash with keys corresponding to the custom attributes
|
123
|
+
# configured with the `custom_access_token_attributes` config option.
|
124
|
+
# A nil value will ignore custom attributes.
|
117
125
|
#
|
118
126
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
119
127
|
# nil if matching record was not found
|
120
128
|
#
|
121
|
-
def find_matching_token(relation, application, scopes)
|
129
|
+
def find_matching_token(relation, application, custom_attributes, scopes)
|
122
130
|
return nil unless relation
|
123
131
|
|
124
132
|
matching_tokens = []
|
@@ -126,7 +134,8 @@ module Doorkeeper
|
|
126
134
|
|
127
135
|
find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
|
128
136
|
tokens = batch.select do |token|
|
129
|
-
scopes_match?(token.scopes, scopes, application&.scopes)
|
137
|
+
scopes_match?(token.scopes, scopes, application&.scopes) &&
|
138
|
+
custom_attributes_match?(token, custom_attributes)
|
130
139
|
end
|
131
140
|
|
132
141
|
matching_tokens.concat(tokens)
|
@@ -160,6 +169,31 @@ module Doorkeeper
|
|
160
169
|
)
|
161
170
|
end
|
162
171
|
|
172
|
+
# Checks whether the token custom attribute values match the custom
|
173
|
+
# attributes from the parameters.
|
174
|
+
#
|
175
|
+
# @param token [Doorkeeper::AccessToken]
|
176
|
+
# The access token whose custom attributes are being compared
|
177
|
+
# to the custom_attributes.
|
178
|
+
#
|
179
|
+
# @param custom_attributes [Hash]
|
180
|
+
# A hash of the attributes for which we want to determine whether
|
181
|
+
# the token's custom attributes match.
|
182
|
+
#
|
183
|
+
# @return [Boolean] true if the token's custom attribute values
|
184
|
+
# match those in the custom_attributes, or if both are empty/blank.
|
185
|
+
# False otherwise.
|
186
|
+
def custom_attributes_match?(token, custom_attributes)
|
187
|
+
return true if custom_attributes.nil?
|
188
|
+
|
189
|
+
token_attribs = token.custom_attributes
|
190
|
+
return true if token_attribs.blank? && custom_attributes.blank?
|
191
|
+
|
192
|
+
Doorkeeper.config.custom_access_token_attributes.all? do |attribute|
|
193
|
+
token_attribs[attribute] == custom_attributes[attribute]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
163
197
|
# Looking for not expired AccessToken record with a matching set of
|
164
198
|
# scopes that belongs to specific Application and Resource Owner.
|
165
199
|
# If it doesn't exists - then creates it.
|
@@ -181,7 +215,9 @@ module Doorkeeper
|
|
181
215
|
#
|
182
216
|
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
183
217
|
if Doorkeeper.config.reuse_access_token
|
184
|
-
|
218
|
+
custom_attributes = extract_custom_attributes(token_attributes).presence
|
219
|
+
access_token = matching_token_for(
|
220
|
+
application, resource_owner, scopes, custom_attributes: custom_attributes, include_expired: false)
|
185
221
|
|
186
222
|
return access_token if access_token&.reusable?
|
187
223
|
end
|
@@ -276,6 +312,18 @@ module Doorkeeper
|
|
276
312
|
def fallback_secret_strategy
|
277
313
|
::Doorkeeper.config.token_secret_fallback_strategy
|
278
314
|
end
|
315
|
+
|
316
|
+
# Extracts the token's custom attributes (defined by the
|
317
|
+
# custom_access_token_attributes config option) from the token's attributes.
|
318
|
+
#
|
319
|
+
# @param attributes [Hash]
|
320
|
+
# A hash of the access token's attributes.
|
321
|
+
# @return [Hash]
|
322
|
+
# A hash containing only the custom access token attributes.
|
323
|
+
def extract_custom_attributes(attributes)
|
324
|
+
attributes.with_indifferent_access.slice(
|
325
|
+
*Doorkeeper.configuration.custom_access_token_attributes)
|
326
|
+
end
|
279
327
|
end
|
280
328
|
|
281
329
|
# Access Token type: Bearer.
|
@@ -308,6 +356,14 @@ module Doorkeeper
|
|
308
356
|
end
|
309
357
|
end
|
310
358
|
|
359
|
+
# The token's custom attributes, as defined by
|
360
|
+
# the custom_access_token_attributes config option.
|
361
|
+
#
|
362
|
+
# @return [Hash] hash of custom access token attributes.
|
363
|
+
def custom_attributes
|
364
|
+
self.class.extract_custom_attributes(attributes)
|
365
|
+
end
|
366
|
+
|
311
367
|
# Indicates whether the token instance have the same credential
|
312
368
|
# as the other Access Token.
|
313
369
|
#
|
@@ -29,6 +29,10 @@ module Doorkeeper
|
|
29
29
|
grant.lock!
|
30
30
|
raise Errors::InvalidGrantReuse if grant.revoked?
|
31
31
|
|
32
|
+
if Doorkeeper.config.revoke_previous_authorization_code_token?
|
33
|
+
revoke_previous_tokens(grant.application, resource_owner)
|
34
|
+
end
|
35
|
+
|
32
36
|
grant.revoke
|
33
37
|
|
34
38
|
find_or_create_access_token(
|
@@ -55,10 +59,16 @@ module Doorkeeper
|
|
55
59
|
Doorkeeper.config.access_grant_model.pkce_supported?
|
56
60
|
end
|
57
61
|
|
62
|
+
def confidential?
|
63
|
+
client&.confidential
|
64
|
+
end
|
65
|
+
|
58
66
|
def validate_params
|
59
67
|
@missing_param =
|
60
68
|
if grant&.uses_pkce? && code_verifier.blank?
|
61
69
|
:code_verifier
|
70
|
+
elsif !confidential? && Doorkeeper.config.force_pkce? && code_verifier.blank?
|
71
|
+
:code_verifier
|
62
72
|
elsif redirect_uri.blank?
|
63
73
|
:redirect_uri
|
64
74
|
end
|
@@ -109,6 +119,10 @@ module Doorkeeper
|
|
109
119
|
.slice(*Doorkeeper.config.custom_access_token_attributes)
|
110
120
|
.symbolize_keys
|
111
121
|
end
|
122
|
+
|
123
|
+
def revoke_previous_tokens(application, resource_owner)
|
124
|
+
Doorkeeper.config.access_token_model.revoke_all_for(application.id, resource_owner)
|
125
|
+
end
|
112
126
|
end
|
113
127
|
end
|
114
128
|
end
|
@@ -5,7 +5,7 @@ module Doorkeeper
|
|
5
5
|
class Client
|
6
6
|
attr_reader :application
|
7
7
|
|
8
|
-
delegate :id, :name, :uid, :redirect_uri, :scopes, to: :@application
|
8
|
+
delegate :id, :name, :uid, :redirect_uri, :scopes, :confidential, to: :@application
|
9
9
|
|
10
10
|
def initialize(application)
|
11
11
|
@application = application
|
@@ -8,7 +8,7 @@ module Doorkeeper
|
|
8
8
|
existing_token = nil
|
9
9
|
|
10
10
|
if lookup_existing_token?
|
11
|
-
existing_token = find_active_existing_token_for(client, scopes)
|
11
|
+
existing_token = find_active_existing_token_for(client, scopes, attributes)
|
12
12
|
return existing_token if Doorkeeper.config.reuse_access_token && existing_token&.reusable?
|
13
13
|
end
|
14
14
|
|
@@ -44,8 +44,11 @@ module Doorkeeper
|
|
44
44
|
Doorkeeper.config.revoke_previous_client_credentials_token?
|
45
45
|
end
|
46
46
|
|
47
|
-
def find_active_existing_token_for(client, scopes)
|
48
|
-
Doorkeeper.config.access_token_model.
|
47
|
+
def find_active_existing_token_for(client, scopes, attributes)
|
48
|
+
custom_attributes = Doorkeeper.config.access_token_model.
|
49
|
+
extract_custom_attributes(attributes).presence
|
50
|
+
Doorkeeper.config.access_token_model.matching_token_for(
|
51
|
+
client, nil, scopes, custom_attributes: custom_attributes, include_expired: false)
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
@@ -14,6 +14,7 @@ module Doorkeeper
|
|
14
14
|
validate :response_type, error: Errors::UnsupportedResponseType
|
15
15
|
validate :response_mode, error: Errors::UnsupportedResponseMode
|
16
16
|
validate :scopes, error: Errors::InvalidScope
|
17
|
+
validate :code_challenge, error: Errors::InvalidCodeChallenge
|
17
18
|
validate :code_challenge_method, error: Errors::InvalidCodeChallengeMethod
|
18
19
|
|
19
20
|
attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
|
@@ -31,7 +32,7 @@ module Doorkeeper
|
|
31
32
|
@code_challenge = parameters[:code_challenge]
|
32
33
|
@code_challenge_method = parameters[:code_challenge_method]
|
33
34
|
@resource_owner = resource_owner
|
34
|
-
@custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes)
|
35
|
+
@custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes).to_h
|
35
36
|
end
|
36
37
|
|
37
38
|
def authorizable?
|
@@ -143,6 +144,12 @@ module Doorkeeper
|
|
143
144
|
)
|
144
145
|
end
|
145
146
|
|
147
|
+
def validate_code_challenge
|
148
|
+
return true unless Doorkeeper.config.force_pkce?
|
149
|
+
return true if client.confidential
|
150
|
+
code_challenge.present?
|
151
|
+
end
|
152
|
+
|
146
153
|
def validate_code_challenge_method
|
147
154
|
return true unless Doorkeeper.config.access_grant_model.pkce_supported?
|
148
155
|
|
@@ -5,12 +5,14 @@ module Doorkeeper
|
|
5
5
|
class TokenResponse
|
6
6
|
attr_reader :token
|
7
7
|
|
8
|
+
alias issued_token token
|
9
|
+
|
8
10
|
def initialize(token)
|
9
11
|
@token = token
|
10
12
|
end
|
11
13
|
|
12
14
|
def body
|
13
|
-
{
|
15
|
+
@body ||= {
|
14
16
|
"access_token" => token.plaintext_token,
|
15
17
|
"token_type" => token.token_type,
|
16
18
|
"expires_in" => token.expires_in_seconds,
|
data/lib/doorkeeper/version.rb
CHANGED
@@ -32,7 +32,7 @@ Doorkeeper.configure do
|
|
32
32
|
# You can use your own model classes if you need to extend (or even override) default
|
33
33
|
# Doorkeeper models such as `Application`, `AccessToken` and `AccessGrant.
|
34
34
|
#
|
35
|
-
#
|
35
|
+
# By default Doorkeeper ActiveRecord ORM uses its own classes:
|
36
36
|
#
|
37
37
|
# access_token_class "Doorkeeper::AccessToken"
|
38
38
|
# access_grant_class "Doorkeeper::AccessGrant"
|
@@ -91,7 +91,10 @@ Doorkeeper.configure do
|
|
91
91
|
# authorization_code_expires_in 10.minutes
|
92
92
|
|
93
93
|
# Access token expiration time (default: 2 hours).
|
94
|
-
# If you
|
94
|
+
# If you set this to `nil` Doorkeeper will not expire the token and omit expires_in in response.
|
95
|
+
# It is RECOMMENDED to set expiration time explicitly.
|
96
|
+
# Prefer access_token_expires_in 100.years or similar,
|
97
|
+
# which would be functionally equivalent and avoid the risk of unexpected behavior by callers.
|
95
98
|
#
|
96
99
|
# access_token_expires_in 2.hours
|
97
100
|
|
@@ -164,6 +167,17 @@ Doorkeeper.configure do
|
|
164
167
|
#
|
165
168
|
# revoke_previous_client_credentials_token
|
166
169
|
|
170
|
+
# Only allow one valid access token obtained via authorization code
|
171
|
+
# per client. If a new access token is obtained before the old one
|
172
|
+
# expired, the old one gets revoked (disabled by default)
|
173
|
+
#
|
174
|
+
# revoke_previous_authorization_code_token
|
175
|
+
|
176
|
+
# Require non-confidential clients to use PKCE when using an authorization code
|
177
|
+
# to obtain an access_token (disabled by default)
|
178
|
+
#
|
179
|
+
# force_pkce
|
180
|
+
|
167
181
|
# Hash access and refresh tokens before persisting them.
|
168
182
|
# This will disable the possibility to use +reuse_access_token+
|
169
183
|
# since plain values can no longer be retrieved.
|
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.
|
4
|
+
version: 5.7.1
|
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: 2024-
|
14
|
+
date: 2024-06-25 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: railties
|
@@ -346,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
346
346
|
- !ruby/object:Gem::Version
|
347
347
|
version: '0'
|
348
348
|
requirements: []
|
349
|
-
rubygems_version: 3.
|
349
|
+
rubygems_version: 3.2.3
|
350
350
|
signing_key:
|
351
351
|
specification_version: 4
|
352
352
|
summary: OAuth 2 provider for Rails and Grape
|