doorkeeper 5.6.6 → 5.7.1
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 +33 -4
- data/README.md +1 -6
- data/app/controllers/doorkeeper/authorizations_controller.rb +16 -7
- 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 +25 -0
- data/lib/doorkeeper/errors.rb +19 -0
- data/lib/doorkeeper/models/access_token_mixin.rb +65 -5
- data/lib/doorkeeper/oauth/authorization_code_request.rb +20 -6
- data/lib/doorkeeper/oauth/base_request.rb +1 -1
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +6 -3
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +5 -4
- data/lib/doorkeeper/oauth/client_credentials/validator.rb +3 -3
- data/lib/doorkeeper/oauth/client_credentials_request.rb +10 -2
- data/lib/doorkeeper/oauth/code_request.rb +1 -1
- data/lib/doorkeeper/oauth/error_response.rb +16 -1
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +1 -1
- data/lib/doorkeeper/oauth/invalid_request_response.rb +4 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +4 -4
- data/lib/doorkeeper/oauth/pre_authorization.rb +19 -12
- data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -5
- data/lib/doorkeeper/oauth/token_introspection.rb +9 -7
- data/lib/doorkeeper/oauth/token_request.rb +1 -1
- data/lib/doorkeeper/oauth/token_response.rb +3 -1
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +1 -1
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/generators/doorkeeper/templates/initializer.rb +23 -3
- metadata +5 -5
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,36 @@ 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.
|
23
|
+
|
24
|
+
## 5.6.9
|
25
|
+
|
26
|
+
- [#1691] Make new Doorkeeper errors backward compatible with older extensions.
|
27
|
+
|
28
|
+
## 5.6.8
|
29
|
+
|
30
|
+
- [#1680] Fix handle_auth_errors :raise NotImplementedError
|
31
|
+
|
32
|
+
## 5.6.7
|
33
|
+
|
34
|
+
- [#1662] Specify uri_redirect validation class explicitly.
|
35
|
+
- [#1652] Add custom attributes support to token generator.
|
36
|
+
- [#1667] Pass `client` instead of `grant.application` to `find_or_create_access_token`.
|
37
|
+
- [#1673] Honor `custom_access_token_attributes` in client credentials grant flow.
|
38
|
+
- [#1676] Improve AuthorizationsController error response handling
|
39
|
+
- [#1677] Fix URIHelper.valid_for_authorization? breaking for non url URIs.
|
11
40
|
|
12
41
|
## 5.6.6
|
13
42
|
|
@@ -16,17 +45,17 @@ User-visible changes worth mentioning.
|
|
16
45
|
- [#1648] Add custom token attributes to Refresh Token Request.
|
17
46
|
- [#1649] Fixed custom_access_token_attributes related errors.
|
18
47
|
|
19
|
-
|
48
|
+
## 5.6.5
|
20
49
|
|
21
50
|
- [#1602] Allow custom data to be stored inside access grants/tokens.
|
22
51
|
- [#1634] Code refactoring for custom token attributes.
|
23
52
|
- [#1639] Add grant type validation to avoid Internal Server Error for DELETE /oauth/authorize endpoint.
|
24
53
|
|
25
|
-
|
54
|
+
## 5.6.4
|
26
55
|
|
27
56
|
- [#1633] Apply ORM configuration in #to_prepare block to avoid autoloading errors.
|
28
57
|
|
29
|
-
|
58
|
+
## 5.6.3
|
30
59
|
|
31
60
|
- [#1622] Drop support for Rubies 2.5 and 2.6
|
32
61
|
- [#1605] Fix URI validation for Ruby 3.2+.
|
data/README.md
CHANGED
@@ -39,7 +39,6 @@ Supported features:
|
|
39
39
|
- [ORMs](#orms)
|
40
40
|
- [Extensions](#extensions)
|
41
41
|
- [Example Applications](#example-applications)
|
42
|
-
- [Tutorials](#tutorials)
|
43
42
|
- [Sponsors](#sponsors)
|
44
43
|
- [Development](#development)
|
45
44
|
- [Contributing](#contributing)
|
@@ -56,7 +55,7 @@ https://github.com/doorkeeper-gem/doorkeeper/releases.
|
|
56
55
|
Additionally, other resources can be found on:
|
57
56
|
|
58
57
|
- [Guides](https://doorkeeper.gitbook.io/guides/) with how-to get started and configuration documentation
|
59
|
-
- See the [Wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki)
|
58
|
+
- See the [Wiki](https://github.com/doorkeeper-gem/doorkeeper/wiki) for articles on how to integrate with other solutions
|
60
59
|
- Screencast from [railscasts.com](http://railscasts.com/): [#353
|
61
60
|
OAuth with
|
62
61
|
Doorkeeper](http://railscasts.com/episodes/353-oauth-with-doorkeeper)
|
@@ -124,10 +123,6 @@ examples](https://github.com/doorkeeper-gem/doorkeeper/wiki/Example-Applications
|
|
124
123
|
in our wiki or follow this [tutorial
|
125
124
|
here](https://github.com/doorkeeper-gem/doorkeeper/wiki/Testing-your-provider-with-OAuth2-gem).
|
126
125
|
|
127
|
-
## Tutorials
|
128
|
-
|
129
|
-
See [list of tutorials](https://github.com/doorkeeper-gem/doorkeeper/wiki#how-tos--tutorials) in order to learn how to use the gem or integrate it with other solutions / gems.
|
130
|
-
|
131
126
|
## Sponsors
|
132
127
|
|
133
128
|
[](#backers)
|
@@ -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
|
@@ -41,18 +41,27 @@ module Doorkeeper
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def render_error
|
44
|
-
if Doorkeeper.
|
45
|
-
|
46
|
-
|
44
|
+
pre_auth.error_response.raise_exception! if Doorkeeper.config.raise_on_errors?
|
45
|
+
|
46
|
+
if Doorkeeper.configuration.redirect_on_errors? && pre_auth.error_response.redirectable?
|
47
|
+
redirect_or_render(pre_auth.error_response)
|
48
|
+
elsif Doorkeeper.configuration.api_only
|
49
|
+
render json: pre_auth.error_response.body, status: pre_auth.error_response.status
|
47
50
|
else
|
48
|
-
render :error, locals: { error_response: pre_auth.error_response }
|
51
|
+
render :error, locals: { error_response: pre_auth.error_response }, status: pre_auth.error_response.status
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
55
|
+
def can_authorize_response?
|
56
|
+
Doorkeeper.config.custom_access_token_attributes.empty? && pre_auth.client.application.confidential? && matching_token?
|
57
|
+
end
|
58
|
+
|
52
59
|
# Active access token issued for the same client and resource owner with
|
53
60
|
# the same set of the scopes exists?
|
54
61
|
def matching_token?
|
55
|
-
|
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(
|
56
65
|
pre_auth.client,
|
57
66
|
current_resource_owner,
|
58
67
|
pre_auth.scopes,
|
@@ -74,7 +83,7 @@ module Doorkeeper
|
|
74
83
|
)
|
75
84
|
end
|
76
85
|
elsif pre_auth.form_post_response?
|
77
|
-
render :form_post
|
86
|
+
render :form_post, locals: { auth: auth }
|
78
87
|
else
|
79
88
|
redirect_to auth.redirect_uri, allow_other_host: true
|
80
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
|
@@ -501,6 +522,10 @@ module Doorkeeper
|
|
501
522
|
handle_auth_errors == :raise
|
502
523
|
end
|
503
524
|
|
525
|
+
def redirect_on_errors?
|
526
|
+
handle_auth_errors == :redirect
|
527
|
+
end
|
528
|
+
|
504
529
|
def application_secret_hashed?
|
505
530
|
instance_variable_defined?(:"@application_secret_strategy")
|
506
531
|
end
|
data/lib/doorkeeper/errors.rb
CHANGED
@@ -39,13 +39,32 @@ module Doorkeeper
|
|
39
39
|
def initialize(response)
|
40
40
|
@response = response
|
41
41
|
end
|
42
|
+
|
43
|
+
def self.name_for_response
|
44
|
+
self.name.demodulize.underscore.to_sym
|
45
|
+
end
|
42
46
|
end
|
43
47
|
|
44
48
|
UnableToGenerateToken = Class.new(DoorkeeperError)
|
45
49
|
TokenGeneratorNotFound = Class.new(DoorkeeperError)
|
46
50
|
NoOrmCleaner = Class.new(DoorkeeperError)
|
47
51
|
|
52
|
+
InvalidRequest = Class.new(BaseResponseError)
|
48
53
|
InvalidToken = Class.new(BaseResponseError)
|
54
|
+
InvalidClient = Class.new(BaseResponseError)
|
55
|
+
InvalidScope = Class.new(BaseResponseError)
|
56
|
+
InvalidRedirectUri = Class.new(BaseResponseError)
|
57
|
+
InvalidCodeChallenge = Class.new(BaseResponseError)
|
58
|
+
InvalidCodeChallengeMethod = Class.new(BaseResponseError)
|
59
|
+
InvalidGrant = Class.new(BaseResponseError)
|
60
|
+
|
61
|
+
UnauthorizedClient = Class.new(BaseResponseError)
|
62
|
+
UnsupportedResponseType = Class.new(BaseResponseError)
|
63
|
+
UnsupportedResponseMode = Class.new(BaseResponseError)
|
64
|
+
|
65
|
+
AccessDenied = Class.new(BaseResponseError)
|
66
|
+
ServerError = Class.new(BaseResponseError)
|
67
|
+
|
49
68
|
TokenExpired = Class.new(InvalidToken)
|
50
69
|
TokenRevoked = Class.new(InvalidToken)
|
51
70
|
TokenUnknown = Class.new(InvalidToken)
|
@@ -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
|
#
|
@@ -435,6 +491,10 @@ module Doorkeeper
|
|
435
491
|
if Doorkeeper.config.polymorphic_resource_owner?
|
436
492
|
attributes[:resource_owner] = resource_owner
|
437
493
|
end
|
494
|
+
|
495
|
+
Doorkeeper.config.custom_access_token_attributes.each do |attribute_name|
|
496
|
+
attributes[attribute_name] = public_send(attribute_name)
|
497
|
+
end
|
438
498
|
end
|
439
499
|
end
|
440
500
|
|
@@ -3,12 +3,12 @@
|
|
3
3
|
module Doorkeeper
|
4
4
|
module OAuth
|
5
5
|
class AuthorizationCodeRequest < BaseRequest
|
6
|
-
validate :params, error:
|
7
|
-
validate :client, error:
|
8
|
-
validate :grant, error:
|
6
|
+
validate :params, error: Errors::InvalidRequest
|
7
|
+
validate :client, error: Errors::InvalidClient
|
8
|
+
validate :grant, error: Errors::InvalidGrant
|
9
9
|
# @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
|
10
|
-
validate :redirect_uri, error:
|
11
|
-
validate :code_verifier, error:
|
10
|
+
validate :redirect_uri, error: Errors::InvalidGrant
|
11
|
+
validate :code_verifier, error: Errors::InvalidGrant
|
12
12
|
|
13
13
|
attr_reader :grant, :client, :redirect_uri, :access_token, :code_verifier,
|
14
14
|
:invalid_request_reason, :missing_param
|
@@ -29,10 +29,14 @@ 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(
|
35
|
-
|
39
|
+
client,
|
36
40
|
resource_owner,
|
37
41
|
grant.scopes,
|
38
42
|
custom_token_attributes_with_data,
|
@@ -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
|
@@ -15,7 +15,7 @@ module Doorkeeper
|
|
15
15
|
@response = TokenResponse.new(access_token)
|
16
16
|
after_successful_response
|
17
17
|
@response
|
18
|
-
elsif error ==
|
18
|
+
elsif error == Errors::InvalidRequest
|
19
19
|
@response = InvalidRequestResponse.from_request(self)
|
20
20
|
else
|
21
21
|
@response = ErrorResponse.from_request(self)
|
@@ -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
|
@@ -11,10 +11,10 @@ module Doorkeeper
|
|
11
11
|
@validator = validator
|
12
12
|
end
|
13
13
|
|
14
|
-
def create(client, scopes, creator = Creator.new)
|
14
|
+
def create(client, scopes, attributes = {}, creator = Creator.new)
|
15
15
|
if validator.valid?
|
16
|
-
@token = create_token(client, scopes, creator)
|
17
|
-
@error =
|
16
|
+
@token = create_token(client, scopes, attributes, creator)
|
17
|
+
@error = Errors::ServerError unless @token
|
18
18
|
else
|
19
19
|
@token = false
|
20
20
|
@error = validator.error
|
@@ -25,7 +25,7 @@ module Doorkeeper
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def create_token(client, scopes, creator)
|
28
|
+
def create_token(client, scopes, attributes, creator)
|
29
29
|
context = Authorization::Token.build_context(
|
30
30
|
client,
|
31
31
|
Doorkeeper::OAuth::CLIENT_CREDENTIALS,
|
@@ -39,6 +39,7 @@ module Doorkeeper
|
|
39
39
|
scopes,
|
40
40
|
use_refresh_token: false,
|
41
41
|
expires_in: ttl,
|
42
|
+
**attributes
|
42
43
|
)
|
43
44
|
end
|
44
45
|
end
|
@@ -7,9 +7,9 @@ module Doorkeeper
|
|
7
7
|
include Validations
|
8
8
|
include OAuth::Helpers
|
9
9
|
|
10
|
-
validate :client, error:
|
11
|
-
validate :client_supports_grant_flow, error:
|
12
|
-
validate :scopes, error:
|
10
|
+
validate :client, error: Errors::InvalidClient
|
11
|
+
validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
|
12
|
+
validate :scopes, error: Errors::InvalidScope
|
13
13
|
|
14
14
|
def initialize(server, request)
|
15
15
|
@server = server
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Doorkeeper
|
4
4
|
module OAuth
|
5
5
|
class ClientCredentialsRequest < BaseRequest
|
6
|
-
attr_reader :client, :original_scopes, :response
|
6
|
+
attr_reader :client, :original_scopes, :parameters, :response
|
7
7
|
|
8
8
|
alias error_response response
|
9
9
|
|
@@ -14,6 +14,7 @@ module Doorkeeper
|
|
14
14
|
@server = server
|
15
15
|
@response = nil
|
16
16
|
@original_scopes = parameters[:scope]
|
17
|
+
@parameters = parameters.except(:scope)
|
17
18
|
end
|
18
19
|
|
19
20
|
def access_token
|
@@ -30,7 +31,14 @@ module Doorkeeper
|
|
30
31
|
private
|
31
32
|
|
32
33
|
def valid?
|
33
|
-
issuer.create(client, scopes)
|
34
|
+
issuer.create(client, scopes, custom_token_attributes_with_data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def custom_token_attributes_with_data
|
38
|
+
parameters
|
39
|
+
.with_indifferent_access
|
40
|
+
.slice(*Doorkeeper.config.custom_access_token_attributes)
|
41
|
+
.symbolize_keys
|
34
42
|
end
|
35
43
|
end
|
36
44
|
end
|
@@ -10,17 +10,31 @@ module Doorkeeper
|
|
10
10
|
def self.from_request(request, attributes = {})
|
11
11
|
new(
|
12
12
|
attributes.merge(
|
13
|
-
name: request.error,
|
13
|
+
name: error_name_for(request.error),
|
14
|
+
exception_class: exception_class_for(request.error),
|
14
15
|
state: request.try(:state),
|
15
16
|
redirect_uri: request.try(:redirect_uri),
|
16
17
|
),
|
17
18
|
)
|
18
19
|
end
|
19
20
|
|
21
|
+
def self.error_name_for(error)
|
22
|
+
error.respond_to?(:name_for_response) ? error.name_for_response : error
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.exception_class_for(error)
|
26
|
+
return error if error.respond_to?(:name_for_response)
|
27
|
+
|
28
|
+
"Doorkeeper::Errors::#{error.to_s.classify}".safe_constantize
|
29
|
+
end
|
30
|
+
|
31
|
+
private_class_method :error_name_for, :exception_class_for
|
32
|
+
|
20
33
|
delegate :name, :description, :state, to: :@error
|
21
34
|
|
22
35
|
def initialize(attributes = {})
|
23
36
|
@error = OAuth::Error.new(*attributes.values_at(:name, :state))
|
37
|
+
@exception_class = attributes[:exception_class]
|
24
38
|
@redirect_uri = attributes[:redirect_uri]
|
25
39
|
@response_on_fragment = attributes[:response_on_fragment]
|
26
40
|
end
|
@@ -72,6 +86,7 @@ module Doorkeeper
|
|
72
86
|
end
|
73
87
|
|
74
88
|
def exception_class
|
89
|
+
return @exception_class if @exception_class
|
75
90
|
raise NotImplementedError, "error response must define #exception_class"
|
76
91
|
end
|
77
92
|
|
@@ -5,10 +5,10 @@ module Doorkeeper
|
|
5
5
|
class PasswordAccessTokenRequest < BaseRequest
|
6
6
|
include OAuth::Helpers
|
7
7
|
|
8
|
-
validate :client, error:
|
9
|
-
validate :client_supports_grant_flow, error:
|
10
|
-
validate :resource_owner, error:
|
11
|
-
validate :scopes, error:
|
8
|
+
validate :client, error: Errors::InvalidClient
|
9
|
+
validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
|
10
|
+
validate :resource_owner, error: Errors::InvalidGrant
|
11
|
+
validate :scopes, error: Errors::InvalidScope
|
12
12
|
|
13
13
|
attr_reader :client, :credentials, :resource_owner, :parameters, :access_token
|
14
14
|
|
@@ -5,16 +5,17 @@ module Doorkeeper
|
|
5
5
|
class PreAuthorization
|
6
6
|
include Validations
|
7
7
|
|
8
|
-
validate :client_id, error:
|
9
|
-
validate :client, error:
|
10
|
-
validate :client_supports_grant_flow, error:
|
11
|
-
validate :resource_owner_authorize_for_client, error:
|
12
|
-
validate :redirect_uri, error:
|
13
|
-
validate :params, error:
|
14
|
-
validate :response_type, error:
|
15
|
-
validate :response_mode, error:
|
16
|
-
validate :scopes, error:
|
17
|
-
validate :
|
8
|
+
validate :client_id, error: Errors::InvalidRequest
|
9
|
+
validate :client, error: Errors::InvalidClient
|
10
|
+
validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
|
11
|
+
validate :resource_owner_authorize_for_client, error: Errors::InvalidClient
|
12
|
+
validate :redirect_uri, error: Errors::InvalidRedirectUri
|
13
|
+
validate :params, error: Errors::InvalidRequest
|
14
|
+
validate :response_type, error: Errors::UnsupportedResponseType
|
15
|
+
validate :response_mode, error: Errors::UnsupportedResponseMode
|
16
|
+
validate :scopes, error: Errors::InvalidScope
|
17
|
+
validate :code_challenge, error: Errors::InvalidCodeChallenge
|
18
|
+
validate :code_challenge_method, error: Errors::InvalidCodeChallengeMethod
|
18
19
|
|
19
20
|
attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
|
20
21
|
:redirect_uri, :resource_owner, :response_type, :state,
|
@@ -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?
|
@@ -47,7 +48,7 @@ module Doorkeeper
|
|
47
48
|
end
|
48
49
|
|
49
50
|
def error_response
|
50
|
-
if error ==
|
51
|
+
if error == Errors::InvalidRequest
|
51
52
|
OAuth::InvalidRequestResponse.from_request(
|
52
53
|
self,
|
53
54
|
response_on_fragment: response_on_fragment?,
|
@@ -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,11 +5,11 @@ module Doorkeeper
|
|
5
5
|
class RefreshTokenRequest < BaseRequest
|
6
6
|
include OAuth::Helpers
|
7
7
|
|
8
|
-
validate :token_presence, error:
|
9
|
-
validate :token, error:
|
10
|
-
validate :client, error:
|
11
|
-
validate :client_match, error:
|
12
|
-
validate :scope, error:
|
8
|
+
validate :token_presence, error: Errors::InvalidRequest
|
9
|
+
validate :token, error: Errors::InvalidGrant
|
10
|
+
validate :client, error: Errors::InvalidClient
|
11
|
+
validate :client_match, error: Errors::InvalidGrant
|
12
|
+
validate :scope, error: Errors::InvalidScope
|
13
13
|
|
14
14
|
attr_reader :access_token, :client, :credentials, :refresh_token
|
15
15
|
attr_reader :missing_param
|
@@ -6,6 +6,8 @@ module Doorkeeper
|
|
6
6
|
#
|
7
7
|
# @see https://datatracker.ietf.org/doc/html/rfc7662
|
8
8
|
class TokenIntrospection
|
9
|
+
attr_reader :error
|
10
|
+
|
9
11
|
def initialize(server, token)
|
10
12
|
@server = server
|
11
13
|
@token = token
|
@@ -20,12 +22,12 @@ module Doorkeeper
|
|
20
22
|
def error_response
|
21
23
|
return if @error.blank?
|
22
24
|
|
23
|
-
if @error ==
|
25
|
+
if @error == Errors::InvalidToken
|
24
26
|
OAuth::InvalidTokenResponse.from_access_token(authorized_token)
|
25
|
-
elsif @error ==
|
27
|
+
elsif @error == Errors::InvalidRequest
|
26
28
|
OAuth::InvalidRequestResponse.from_request(self)
|
27
29
|
else
|
28
|
-
OAuth::ErrorResponse.
|
30
|
+
OAuth::ErrorResponse.from_request(self)
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
@@ -36,7 +38,7 @@ module Doorkeeper
|
|
36
38
|
private
|
37
39
|
|
38
40
|
attr_reader :server, :token
|
39
|
-
attr_reader :
|
41
|
+
attr_reader :invalid_request_reason
|
40
42
|
|
41
43
|
# If the protected resource uses OAuth 2.0 client credentials to
|
42
44
|
# authenticate to the introspection endpoint and its credentials are
|
@@ -58,7 +60,7 @@ module Doorkeeper
|
|
58
60
|
def authorize!
|
59
61
|
# Requested client authorization
|
60
62
|
if server.credentials
|
61
|
-
@error =
|
63
|
+
@error = Errors::InvalidClient unless authorized_client
|
62
64
|
elsif authorized_token
|
63
65
|
# Requested bearer token authorization
|
64
66
|
#
|
@@ -69,9 +71,9 @@ module Doorkeeper
|
|
69
71
|
# HTTP 401 code as described in Section 3 of OAuth 2.0 Bearer Token
|
70
72
|
# Usage [RFC6750].
|
71
73
|
#
|
72
|
-
@error =
|
74
|
+
@error = Errors::InvalidToken unless valid_authorized_token?
|
73
75
|
else
|
74
|
-
@error =
|
76
|
+
@error = Errors::InvalidRequest
|
75
77
|
@invalid_request_reason = :request_not_authorized
|
76
78
|
end
|
77
79
|
end
|
@@ -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,
|
@@ -22,7 +22,7 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
22
22
|
|
23
23
|
validates :name, :secret, :uid, presence: true
|
24
24
|
validates :uid, uniqueness: { case_sensitive: true }
|
25
|
-
|
25
|
+
validates_with Doorkeeper::RedirectUriValidator, attributes: [:redirect_uri]
|
26
26
|
validates :confidential, inclusion: { in: [true, false] }
|
27
27
|
|
28
28
|
validate :scopes_match_configured, if: :enforce_scopes?
|
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.
|
@@ -312,6 +326,12 @@ Doorkeeper.configure do
|
|
312
326
|
# Doorkeeper::Errors::TokenRevoked, Doorkeeper::Errors::TokenUnknown
|
313
327
|
#
|
314
328
|
# handle_auth_errors :raise
|
329
|
+
#
|
330
|
+
# If you want to redirect back to the client application in accordance with
|
331
|
+
# https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1, you can set
|
332
|
+
# +handle_auth_errors+ to :redirect
|
333
|
+
#
|
334
|
+
# handle_auth_errors :redirect
|
315
335
|
|
316
336
|
# Customize token introspection response.
|
317
337
|
# Allows to add your own fields to default one that are required by the OAuth spec
|
@@ -385,7 +405,7 @@ Doorkeeper.configure do
|
|
385
405
|
# true in case resource owner authorized for the specific application or false in other
|
386
406
|
# cases.
|
387
407
|
#
|
388
|
-
#
|
408
|
+
# By default all Resource Owners are authorized to any Client (application).
|
389
409
|
#
|
390
410
|
# authorize_resource_owner_for_client do |client, resource_owner|
|
391
411
|
# resource_owner.admin? || client.owners_allowlist.include?(resource_owner)
|
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:
|
14
|
+
date: 2024-06-25 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: railties
|
@@ -103,14 +103,14 @@ dependencies:
|
|
103
103
|
requirements:
|
104
104
|
- - "~>"
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version: 0.
|
106
|
+
version: 0.10.0
|
107
107
|
type: :development
|
108
108
|
prerelease: false
|
109
109
|
version_requirements: !ruby/object:Gem::Requirement
|
110
110
|
requirements:
|
111
111
|
- - "~>"
|
112
112
|
- !ruby/object:Gem::Version
|
113
|
-
version: 0.
|
113
|
+
version: 0.10.0
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: grape
|
116
116
|
requirement: !ruby/object:Gem::Requirement
|
@@ -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
|