doorkeeper 5.6.6 → 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 +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
|
[![OpenCollective](https://opencollective.com/doorkeeper-gem/backers/badge.svg)](#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
|