doorkeeper 5.6.6 → 5.7.1

Sign up to get free protection for your applications and to get access to all the features.
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