doorkeeper 5.6.9 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bf87231b7fa13bb47db61868954af3c01b07b0909ac95abb2de15d1064dd423
4
- data.tar.gz: f6dda19eae61f69331fc338fbb1803a7a94c563b4dc46ea694bee37c430db1b7
3
+ metadata.gz: aeb5db3b840c0c214129e69fb029c1daf95974b033856ab550e9abd3b529b0c1
4
+ data.tar.gz: 6f948a7fa2b8a2796c56e94a762b1e256228c13f972bbd056d1b4e246bea5001
5
5
  SHA512:
6
- metadata.gz: f0d54d75716f74f23f35ab9400683a805e3a4c7483c11ea5be10669abe38385c7625f9304379712f661bea026d49cb5fe8ddc1b6653cf66115279edc1f785096
7
- data.tar.gz: 68c841037b9544b1bdfdc36405169ad8cf942b6c32b6d1023261bb7c37116bab602a8c12b209d3b006219eaf0701d7a705ac6cdec49004c19ae918d044377d53
6
+ metadata.gz: 2da3aca020ed25ba334d5dfbcbffd1c46818eaa79bbc67e38bcf01dada8105c6683bfb5552dae1cdce34a68e5baafa0c9a9b49b5c980f78464925f2f7576b873
7
+ data.tar.gz: 865a9c03a28b309624e8058a251c92d002199f9072015efafafb622fad5878274042bee32aba169a0395fc1ba0558ca86c1bd7b32b9dc288c5136b69c6a68e69
data/CHANGELOG.md CHANGED
@@ -7,7 +7,15 @@ User-visible changes worth mentioning.
7
7
 
8
8
  ## main
9
9
 
10
- - [#PR ID] Add your changelog here.
10
+ Add your entry here.
11
+
12
+ ## 5.7.0
13
+
14
+ - [#1696] Add missing `#issued_token` method to `OAuth::TokenResponse`
15
+ - [#1697] Allow a TokenResponse body to be customized (memoize response body).
16
+ - [#1702] Fix bugs for error response in the form_post and error view
17
+ - [#1660] Custom access token attributes are now considered when finding matching tokens (fixes #1665).
18
+ Introduce `revoke_previous_client_credentials_token` configuration option.
11
19
 
12
20
  ## 5.6.9
13
21
 
@@ -33,17 +41,17 @@ User-visible changes worth mentioning.
33
41
  - [#1648] Add custom token attributes to Refresh Token Request.
34
42
  - [#1649] Fixed custom_access_token_attributes related errors.
35
43
 
36
- # 5.6.5
44
+ ## 5.6.5
37
45
 
38
46
  - [#1602] Allow custom data to be stored inside access grants/tokens.
39
47
  - [#1634] Code refactoring for custom token attributes.
40
48
  - [#1639] Add grant type validation to avoid Internal Server Error for DELETE /oauth/authorize endpoint.
41
49
 
42
- # 5.6.4
50
+ ## 5.6.4
43
51
 
44
52
  - [#1633] Apply ORM configuration in #to_prepare block to avoid autoloading errors.
45
53
 
46
- # 5.6.3
54
+ ## 5.6.3
47
55
 
48
56
  - [#1622] Drop support for Rubies 2.5 and 2.6
49
57
  - [#1605] Fix URI validation for Ruby 3.2+.
@@ -31,7 +31,7 @@ module Doorkeeper
31
31
  private
32
32
 
33
33
  def render_success
34
- if skip_authorization? || (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
@@ -52,10 +52,16 @@ module Doorkeeper
52
52
  end
53
53
  end
54
54
 
55
+ def can_authorize_response?
56
+ Doorkeeper.config.custom_access_token_attributes.empty? && pre_auth.client.application.confidential? && matching_token?
57
+ end
58
+
55
59
  # Active access token issued for the same client and resource owner with
56
60
  # the same set of the scopes exists?
57
61
  def matching_token?
58
- 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(
59
65
  pre_auth.client,
60
66
  current_resource_owner,
61
67
  pre_auth.scopes,
@@ -77,7 +83,7 @@ module Doorkeeper
77
83
  )
78
84
  end
79
85
  elsif pre_auth.form_post_response?
80
- render :form_post
86
+ render :form_post, locals: { auth: auth }
81
87
  else
82
88
  redirect_to auth.redirect_uri, allow_other_host: true
83
89
  end
@@ -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,13 @@ 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
+
109
116
  # Use an API mode for applications generated with --api argument
110
117
  # It will skip applications controller, disable forgery protection
111
118
  def api_only
@@ -481,6 +488,10 @@ module Doorkeeper
481
488
  option_set? :revoke_previous_client_credentials_token
482
489
  end
483
490
 
491
+ def revoke_previous_authorization_code_token?
492
+ option_set? :revoke_previous_authorization_code_token
493
+ end
494
+
484
495
  def enforce_configured_scopes?
485
496
  option_set? :enforce_configured_scopes
486
497
  end
@@ -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
  #
@@ -29,6 +29,10 @@ module Doorkeeper
29
29
  grant.lock!
30
30
  raise Errors::InvalidGrantReuse if grant.revoked?
31
31
 
32
+ if Doorkeeper.config.revoke_previous_authorization_code_token?
33
+ revoke_previous_tokens(grant.application, resource_owner)
34
+ end
35
+
32
36
  grant.revoke
33
37
 
34
38
  find_or_create_access_token(
@@ -109,6 +113,10 @@ module Doorkeeper
109
113
  .slice(*Doorkeeper.config.custom_access_token_attributes)
110
114
  .symbolize_keys
111
115
  end
116
+
117
+ def revoke_previous_tokens(application, resource_owner)
118
+ Doorkeeper.config.access_token_model.revoke_all_for(application.id, resource_owner)
119
+ end
112
120
  end
113
121
  end
114
122
  end
@@ -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
@@ -31,7 +31,7 @@ module Doorkeeper
31
31
  @code_challenge = parameters[:code_challenge]
32
32
  @code_challenge_method = parameters[:code_challenge_method]
33
33
  @resource_owner = resource_owner
34
- @custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes)
34
+ @custom_access_token_attributes = parameters.slice(*Doorkeeper.config.custom_access_token_attributes).to_h
35
35
  end
36
36
 
37
37
  def authorizable?
@@ -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,
@@ -4,8 +4,8 @@ module Doorkeeper
4
4
  module VERSION
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
- MINOR = 6
8
- TINY = 9
7
+ MINOR = 7
8
+ TINY = 0
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,12 @@ 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
+
167
176
  # Hash access and refresh tokens before persisting them.
168
177
  # This will disable the possibility to use +reuse_access_token+
169
178
  # since plain values can no longer be retrieved.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.9
4
+ version: 5.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2024-02-14 00:00:00.000000000 Z
14
+ date: 2024-04-24 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -346,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
346
346
  - !ruby/object:Gem::Version
347
347
  version: '0'
348
348
  requirements: []
349
- rubygems_version: 3.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