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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -4
  3. data/README.md +1 -6
  4. data/app/controllers/doorkeeper/authorizations_controller.rb +16 -7
  5. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  6. data/app/views/doorkeeper/authorizations/form_post.html.erb +1 -1
  7. data/lib/doorkeeper/config.rb +25 -0
  8. data/lib/doorkeeper/errors.rb +19 -0
  9. data/lib/doorkeeper/models/access_token_mixin.rb +65 -5
  10. data/lib/doorkeeper/oauth/authorization_code_request.rb +20 -6
  11. data/lib/doorkeeper/oauth/base_request.rb +1 -1
  12. data/lib/doorkeeper/oauth/client.rb +1 -1
  13. data/lib/doorkeeper/oauth/client_credentials/creator.rb +6 -3
  14. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +5 -4
  15. data/lib/doorkeeper/oauth/client_credentials/validator.rb +3 -3
  16. data/lib/doorkeeper/oauth/client_credentials_request.rb +10 -2
  17. data/lib/doorkeeper/oauth/code_request.rb +1 -1
  18. data/lib/doorkeeper/oauth/error_response.rb +16 -1
  19. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +1 -1
  20. data/lib/doorkeeper/oauth/invalid_request_response.rb +4 -0
  21. data/lib/doorkeeper/oauth/password_access_token_request.rb +4 -4
  22. data/lib/doorkeeper/oauth/pre_authorization.rb +19 -12
  23. data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -5
  24. data/lib/doorkeeper/oauth/token_introspection.rb +9 -7
  25. data/lib/doorkeeper/oauth/token_request.rb +1 -1
  26. data/lib/doorkeeper/oauth/token_response.rb +3 -1
  27. data/lib/doorkeeper/orm/active_record/mixins/application.rb +1 -1
  28. data/lib/doorkeeper/version.rb +2 -2
  29. data/lib/generators/doorkeeper/templates/initializer.rb +23 -3
  30. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b62a0472a97d06b40362817c9d5c0dd7dd6e0d0e600437a19f5cf2fd18c4be46
4
- data.tar.gz: 9850cef14c21a1f0df2fb451a485ab5b8066360a3008124f7aed287409364e36
3
+ metadata.gz: df8ee24bf06e6b24c9ee822c24abf45ce0424b93ff05361dcdff76c930fa3c5a
4
+ data.tar.gz: 56e84b30480a60d02eea4b417f41c1cd6b322365bfce0e9fa31aad504def3807
5
5
  SHA512:
6
- metadata.gz: de0c7021c4735b26249e5b267db11ede06f55b23d8f9bd51641d1cf3eee3812e14a2deec986e8aa6ee81de98097083fdb634a441fd4928cb47286fa977ba5d96
7
- data.tar.gz: 3865639c837771ceeafceec8a110e506f88fef45c61f7274782c637e794f9185be18ee98270852bac6fecb0fc90e4893dfed08d715c761507e87396e5a559bc2
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
- - [#ID] Add your PR description here.
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
- # 5.6.5
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
- # 5.6.4
54
+ ## 5.6.4
26
55
 
27
56
  - [#1633] Apply ORM configuration in #to_prepare block to avoid autoloading errors.
28
57
 
29
- # 5.6.3
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) with articles and other documentation
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? || (matching_token? && pre_auth.client.application.confidential?)
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.configuration.api_only
45
- render json: pre_auth.error_response.body,
46
- status: :bad_request
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
- Doorkeeper.config.access_token_model.matching_token_for(
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
@@ -4,6 +4,6 @@
4
4
 
5
5
  <main role="main">
6
6
  <pre>
7
- <%= (respond_to?(:error_response) ? error_response : @pre_auth.error_response).body[:error_description] %>
7
+ <%= (local_assigns[:error_response] ? error_response : @pre_auth.error_response).body[:error_description] %>
8
8
  </pre>
9
9
  </main>
@@ -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
- <% @authorize_response.body.compact.each do |key, value| %>
6
+ <% auth.body.compact.each do |key, value| %>
7
7
  <%= hidden_field_tag key, value %>
8
8
  <% end %>
9
9
  <% end %>
@@ -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
@@ -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
- access_token = matching_token_for(application, resource_owner, scopes, include_expired: false)
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: :invalid_request
7
- validate :client, error: :invalid_client
8
- validate :grant, error: :invalid_grant
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: :invalid_grant
11
- validate :code_verifier, error: :invalid_grant
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
- grant.application,
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 == :invalid_request
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.matching_token_for(client, nil, scopes, include_expired: false)
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 = :server_error unless @token
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: :invalid_client
11
- validate :client_supports_grant_flow, error: :unauthorized_client
12
- validate :scopes, error: :invalid_scope
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
@@ -17,7 +17,7 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  def deny
20
- pre_auth.error = :access_denied
20
+ pre_auth.error = Errors::AccessDenied
21
21
  pre_auth.error_response
22
22
  end
23
23
  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
 
@@ -40,7 +40,7 @@ module Doorkeeper
40
40
 
41
41
  def self.loopback_uri?(uri)
42
42
  IPAddr.new(uri.host).loopback?
43
- rescue IPAddr::Error
43
+ rescue IPAddr::Error, IPAddr::InvalidAddressError
44
44
  false
45
45
  end
46
46
 
@@ -35,6 +35,10 @@ module Doorkeeper
35
35
  )
36
36
  end
37
37
 
38
+ def exception_class
39
+ Doorkeeper::Errors::InvalidRequest
40
+ end
41
+
38
42
  def redirectable?
39
43
  super && @missing_param != :client_id
40
44
  end
@@ -5,10 +5,10 @@ module Doorkeeper
5
5
  class PasswordAccessTokenRequest < BaseRequest
6
6
  include OAuth::Helpers
7
7
 
8
- validate :client, error: :invalid_client
9
- validate :client_supports_grant_flow, error: :unauthorized_client
10
- validate :resource_owner, error: :invalid_grant
11
- validate :scopes, error: :invalid_scope
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: :invalid_request
9
- validate :client, error: :invalid_client
10
- validate :client_supports_grant_flow, error: :unauthorized_client
11
- validate :resource_owner_authorize_for_client, error: :invalid_client
12
- validate :redirect_uri, error: :invalid_redirect_uri
13
- validate :params, error: :invalid_request
14
- validate :response_type, error: :unsupported_response_type
15
- validate :response_mode, error: :unsupported_response_mode
16
- validate :scopes, error: :invalid_scope
17
- validate :code_challenge_method, error: :invalid_code_challenge_method
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 == :invalid_request
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: :invalid_request
9
- validate :token, error: :invalid_grant
10
- validate :client, error: :invalid_client
11
- validate :client_match, error: :invalid_grant
12
- validate :scope, error: :invalid_scope
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 == :invalid_token
25
+ if @error == Errors::InvalidToken
24
26
  OAuth::InvalidTokenResponse.from_access_token(authorized_token)
25
- elsif @error == :invalid_request
27
+ elsif @error == Errors::InvalidRequest
26
28
  OAuth::InvalidRequestResponse.from_request(self)
27
29
  else
28
- OAuth::ErrorResponse.new(name: @error)
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 :error, :invalid_request_reason
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 = :invalid_client unless authorized_client
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 = :invalid_token unless valid_authorized_token?
74
+ @error = Errors::InvalidToken unless valid_authorized_token?
73
75
  else
74
- @error = :invalid_request
76
+ @error = Errors::InvalidRequest
75
77
  @invalid_request_reason = :request_not_authorized
76
78
  end
77
79
  end
@@ -17,7 +17,7 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  def deny
20
- pre_auth.error = :access_denied
20
+ pre_auth.error = Errors::AccessDenied
21
21
  pre_auth.error_response
22
22
  end
23
23
  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
- validates :redirect_uri, "doorkeeper/redirect_uri": true
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?
@@ -4,8 +4,8 @@ module Doorkeeper
4
4
  module VERSION
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
- MINOR = 6
8
- TINY = 6
7
+ MINOR = 7
8
+ TINY = 1
9
9
  PRE = nil
10
10
 
11
11
  # Full version number
@@ -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
- # Be default Doorkeeper ActiveRecord ORM uses it's own classes:
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 want to disable expiration, set this to `nil`.
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
- # Be default all Resource Owners are authorized to any Client (application).
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.6.6
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: 2023-03-29 00:00:00.000000000 Z
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.9.3
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.9.3
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.1.6
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