doorkeeper 5.8.0 → 5.8.2

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: f4907be020648eb5c1dbc6e596c31c72c4133740e849ee1345ff337a4ce01c56
4
- data.tar.gz: 831e491b48efe921e43065d393c6a785a9988e6057f1ed3e1e4f2e8626555e65
3
+ metadata.gz: de574cec8c17af2fd1026081acc0bf592c71ecbf947d92546df6b4d48ce3b5ce
4
+ data.tar.gz: eb282ce352bbd4491014b753535ff24e804ca801e02aef8a6ded3f7ca5951e64
5
5
  SHA512:
6
- metadata.gz: 2f97a8c5d6749cea33b31737988b26271d1ad03f4bd8f003b3674e8e81159a44e6a54f5611b868a06a9a074995bcc47165f222aadcd961746cc43776ee993c57
7
- data.tar.gz: 3d2e13efa8bba0cedc4a63055d6ecb70f0af8d1dc0644047b4b1ecb3bc3bf5affd2a0a20d46989a4999b4361573c39ae72f516398c67ffe708512f0fd6e23011
6
+ metadata.gz: 68b668d79eb5532cb4dbe660eb26269d67eb545b5dbd12bae15c087752c7e5447e1f60326725c0d838189a746de5718e8644605dcdd946d4f2cf29a556297369
7
+ data.tar.gz: 8ad2d79f707129abd0787710cc86ee563af9887f986dec03edc396c691ffb26e18279b3693bc15658d059e78dd7dc2ce3093a137c03bb16e7858832d9e80c368
data/CHANGELOG.md CHANGED
@@ -9,6 +9,20 @@ User-visible changes worth mentioning.
9
9
 
10
10
  Add your entry here.
11
11
 
12
+ ## 5.8.2
13
+
14
+ - [#1755] Fix the error message for force_pkce
15
+ - [#1761] Memoize authentication failure
16
+ - [#1762] Allow missing client to trigger invalid client error when force_pkce is enabled
17
+ - [#1767] Make sure error handling happens on a controller level opposed to action level to account for the controller being extended
18
+
19
+ ## 5.8.1
20
+
21
+ - [#1752] Bump the range of supported Ruby and Rails versions
22
+ - [#1747] Fix unknown pkce method error when configured
23
+ - [#1744] Allow for expired refresh tokens to be revoked
24
+ - [#1754] Fix refresh tokens with dynamic scopes
25
+
12
26
  ## 5.8.0
13
27
 
14
28
  - [#1739] Add support for dynamic scopes
@@ -4,12 +4,14 @@ module Doorkeeper
4
4
  class TokensController < Doorkeeper::ApplicationMetalController
5
5
  before_action :validate_presence_of_client, only: [:revoke]
6
6
 
7
+ rescue_from Errors::DoorkeeperError do |e|
8
+ handle_token_exception(e)
9
+ end
10
+
7
11
  def create
8
12
  headers.merge!(authorize_response.headers)
9
13
  render json: authorize_response.body,
10
14
  status: authorize_response.status
11
- rescue Errors::DoorkeeperError => e
12
- handle_token_exception(e)
13
15
  end
14
16
 
15
17
  # OAuth 2.0 Token Revocation - https://datatracker.ietf.org/doc/html/rfc7009
@@ -113,19 +115,38 @@ module Doorkeeper
113
115
  # The authorization server responds with HTTP status code 200 if the token
114
116
  # has been revoked successfully or if the client submitted an invalid
115
117
  # token
116
- token.revoke if token&.accessible?
118
+ revocable_token.revoke if revocable_token.revocable?
117
119
  end
118
120
 
119
121
  def token
120
- @token ||=
122
+ revocable_token&.token
123
+ end
124
+
125
+ def revocable_token
126
+ return @revocable_token if defined? @revocable_token
127
+
128
+ @revocable_token =
121
129
  if params[:token_type_hint] == "refresh_token"
122
- Doorkeeper.config.access_token_model.by_refresh_token(params["token"])
130
+ refresh_token
123
131
  else
124
- Doorkeeper.config.access_token_model.by_token(params["token"]) ||
125
- Doorkeeper.config.access_token_model.by_refresh_token(params["token"])
132
+ access_token || refresh_token
126
133
  end
127
134
  end
128
135
 
136
+ def refresh_token
137
+ token = Doorkeeper.config.access_token_model.by_refresh_token(params["token"])
138
+ return unless token
139
+
140
+ RevocableTokens::RevocableRefreshToken.new(token)
141
+ end
142
+
143
+ def access_token
144
+ token = Doorkeeper.config.access_token_model.by_token(params["token"])
145
+ return unless token
146
+
147
+ RevocableTokens::RevocableAccessToken.new(token)
148
+ end
149
+
129
150
  def strategy
130
151
  @strategy ||= server.token_request(params[:grant_type])
131
152
  end
@@ -96,11 +96,15 @@ en:
96
96
  unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
97
97
  missing_param: 'Missing required parameter: %{value}.'
98
98
  request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
99
+ invalid_code_challenge: 'Code challenge is required.'
99
100
  invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
100
101
  unauthorized_client: 'The client is not authorized to perform this request using this method.'
101
102
  access_denied: 'The resource owner or authorization server denied the request.'
102
103
  invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
103
- invalid_code_challenge_method: 'The code challenge method must be plain or S256.'
104
+ invalid_code_challenge_method:
105
+ zero: 'The authorization server does not support PKCE as there are no accepted code_challenge_method values.'
106
+ one: 'The code_challenge_method must be %{challenge_methods}.'
107
+ other: 'The code_challenge_method must be one of %{challenge_methods}.'
104
108
  server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
105
109
  temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
106
110
 
@@ -6,6 +6,10 @@ module Doorkeeper
6
6
  def type
7
7
  message
8
8
  end
9
+
10
+ def self.translate_options
11
+ {}
12
+ end
9
13
  end
10
14
 
11
15
  class InvalidGrantReuse < DoorkeeperError
@@ -45,6 +49,16 @@ module Doorkeeper
45
49
  end
46
50
  end
47
51
 
52
+ class InvalidCodeChallengeMethod < BaseResponseError
53
+ def self.translate_options
54
+ challenge_methods = Doorkeeper.config.pkce_code_challenge_methods_supported
55
+ {
56
+ challenge_methods: challenge_methods.join(", "),
57
+ count: challenge_methods.length
58
+ }
59
+ end
60
+ end
61
+
48
62
  UnableToGenerateToken = Class.new(DoorkeeperError)
49
63
  TokenGeneratorNotFound = Class.new(DoorkeeperError)
50
64
  NoOrmCleaner = Class.new(DoorkeeperError)
@@ -54,8 +68,6 @@ module Doorkeeper
54
68
  InvalidClient = Class.new(BaseResponseError)
55
69
  InvalidScope = Class.new(BaseResponseError)
56
70
  InvalidRedirectUri = Class.new(BaseResponseError)
57
- InvalidCodeChallenge = Class.new(BaseResponseError)
58
- InvalidCodeChallengeMethod = Class.new(BaseResponseError)
59
71
  InvalidGrant = Class.new(BaseResponseError)
60
72
 
61
73
  UnauthorizedClient = Class.new(BaseResponseError)
@@ -59,15 +59,11 @@ module Doorkeeper
59
59
  Doorkeeper.config.access_grant_model.pkce_supported?
60
60
  end
61
61
 
62
- def confidential?
63
- client&.confidential
64
- end
65
-
66
62
  def validate_params
67
63
  @missing_param =
68
64
  if grant&.uses_pkce? && code_verifier.blank?
69
65
  :code_verifier
70
- elsif !confidential? && Doorkeeper.config.force_pkce? && code_verifier.blank?
66
+ elsif client && !client.confidential && Doorkeeper.config.force_pkce? && code_verifier.blank?
71
67
  :code_verifier
72
68
  elsif redirect_uri.blank?
73
69
  :redirect_uri
@@ -59,7 +59,8 @@ module Doorkeeper
59
59
  client_scopes = @client&.scopes
60
60
  return default_scopes if client_scopes.blank?
61
61
 
62
- default_scopes & client_scopes
62
+ # Avoid using Scope#& for dynamic scopes
63
+ client_scopes.allowed(default_scopes)
63
64
  end
64
65
  end
65
66
  end
@@ -2,13 +2,14 @@
2
2
 
3
3
  module Doorkeeper
4
4
  module OAuth
5
- Error = Struct.new(:name, :state) do
5
+ Error = Struct.new(:name, :state, :translate_options) do
6
6
  def description
7
- I18n.translate(
8
- name,
7
+ options = (translate_options || {}).merge(
9
8
  scope: %i[doorkeeper errors messages],
10
9
  default: :server_error,
11
10
  )
11
+
12
+ I18n.translate(name, **options)
12
13
  end
13
14
  end
14
15
  end
@@ -12,6 +12,7 @@ module Doorkeeper
12
12
  attributes.merge(
13
13
  name: error_name_for(request.error),
14
14
  exception_class: exception_class_for(request.error),
15
+ translate_options: request.error.try(:translate_options),
15
16
  state: request.try(:state),
16
17
  redirect_uri: request.try(:redirect_uri),
17
18
  ),
@@ -33,7 +34,7 @@ module Doorkeeper
33
34
  delegate :name, :description, :state, to: :@error
34
35
 
35
36
  def initialize(attributes = {})
36
- @error = OAuth::Error.new(*attributes.values_at(:name, :state))
37
+ @error = OAuth::Error.new(*attributes.values_at(:name, :state, :translate_options))
37
38
  @exception_class = attributes[:exception_class]
38
39
  @redirect_uri = attributes[:redirect_uri]
39
40
  @response_on_fragment = attributes[:response_on_fragment]
@@ -14,12 +14,13 @@ module Doorkeeper
14
14
  validate :response_type, error: Errors::UnsupportedResponseType
15
15
  validate :response_mode, error: Errors::UnsupportedResponseMode
16
16
  validate :scopes, error: Errors::InvalidScope
17
- validate :code_challenge, error: Errors::InvalidCodeChallenge
17
+ validate :code_challenge, error: Errors::InvalidRequest
18
18
  validate :code_challenge_method, error: Errors::InvalidCodeChallengeMethod
19
19
 
20
20
  attr_reader :client, :code_challenge, :code_challenge_method, :missing_param,
21
21
  :redirect_uri, :resource_owner, :response_type, :state,
22
- :authorization_response_flow, :response_mode, :custom_access_token_attributes
22
+ :authorization_response_flow, :response_mode, :custom_access_token_attributes,
23
+ :invalid_request_reason
23
24
 
24
25
  def initialize(server, parameters = {}, resource_owner = nil)
25
26
  @server = server
@@ -75,7 +76,7 @@ module Doorkeeper
75
76
  if client_scopes.blank?
76
77
  server.default_scopes.to_s
77
78
  else
78
- (server.default_scopes & client_scopes).to_s
79
+ server.default_scopes.allowed(client_scopes).to_s
79
80
  end
80
81
  end
81
82
 
@@ -147,7 +148,10 @@ module Doorkeeper
147
148
  def validate_code_challenge
148
149
  return true unless Doorkeeper.config.force_pkce?
149
150
  return true if client.confidential
150
- code_challenge.present?
151
+ return true if code_challenge.present?
152
+
153
+ @invalid_request_reason = :invalid_code_challenge
154
+ false
151
155
  end
152
156
 
153
157
  def validate_code_challenge_method
@@ -70,10 +70,28 @@ module Doorkeeper
70
70
  end
71
71
  end
72
72
 
73
+ # DEPRECATED: With dynamic scopes, #allowed should be called because
74
+ # A & B doesn't really make sense with dynamic scopes.
75
+ #
76
+ # For example, if A = user:* and B is user:1, A & B = [].
77
+ # If we modified this method to take dynamic scopes into an account, then order
78
+ # becomes important, and this would violate the principle that A & B = B & A.
73
79
  def &(other)
80
+ return allowed(other) if dynamic_scopes_enabled?
81
+
74
82
  self.class.from_array(all & to_array(other))
75
83
  end
76
84
 
85
+ # Returns a set of scopes that are allowed, taking dynamic
86
+ # scopes into account. This instance's scopes is taken as the allowed set,
87
+ # and the passed value is the set to filter.
88
+ #
89
+ # @param other The set of scopes to filter
90
+ def allowed(other)
91
+ filtered_scopes = other.select { |scope| self.exists?(scope) }
92
+ self.class.from_array(filtered_scopes)
93
+ end
94
+
77
95
  private
78
96
 
79
97
  def dynamic_scopes_enabled?
@@ -70,7 +70,9 @@ module Doorkeeper
70
70
  end
71
71
 
72
72
  def doorkeeper_token
73
- @doorkeeper_token ||= OAuth::Token.authenticate(
73
+ return @doorkeeper_token if defined?(@doorkeeper_token)
74
+
75
+ @doorkeeper_token = OAuth::Token.authenticate(
74
76
  request,
75
77
  *Doorkeeper.config.access_token_methods,
76
78
  )
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module RevocableTokens
5
+ class RevocableAccessToken
6
+ attr_reader :token
7
+
8
+ def initialize(token)
9
+ @token = token
10
+ end
11
+
12
+ def revocable?
13
+ token.accessible?
14
+ end
15
+
16
+ def revoke
17
+ token.revoke
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module RevocableTokens
5
+ class RevocableRefreshToken
6
+ attr_reader :token
7
+
8
+ def initialize(token)
9
+ @token = token
10
+ end
11
+
12
+ def revocable?
13
+ !token.revoked?
14
+ end
15
+
16
+ def revoke
17
+ token.revoke
18
+ end
19
+ end
20
+ end
21
+ end
@@ -5,7 +5,7 @@ module Doorkeeper
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
7
  MINOR = 8
8
- TINY = 0
8
+ TINY = 2
9
9
  PRE = nil
10
10
 
11
11
  # Full version number
data/lib/doorkeeper.rb CHANGED
@@ -34,6 +34,11 @@ module Doorkeeper
34
34
  autoload :Token, "doorkeeper/request/token"
35
35
  end
36
36
 
37
+ module RevocableTokens
38
+ autoload :RevocableAccessToken, "doorkeeper/revocable_tokens/revocable_access_token"
39
+ autoload :RevocableRefreshToken, "doorkeeper/revocable_tokens/revocable_refresh_token"
40
+ end
41
+
37
42
  module OAuth
38
43
  autoload :BaseRequest, "doorkeeper/oauth/base_request"
39
44
  autoload :AuthorizationCodeRequest, "doorkeeper/oauth/authorization_code_request"
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.8.0
4
+ version: 5.8.2
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-10-31 00:00:00.000000000 Z
14
+ date: 2025-04-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -290,6 +290,8 @@ files:
290
290
  - lib/doorkeeper/request/refresh_token.rb
291
291
  - lib/doorkeeper/request/strategy.rb
292
292
  - lib/doorkeeper/request/token.rb
293
+ - lib/doorkeeper/revocable_tokens/revocable_access_token.rb
294
+ - lib/doorkeeper/revocable_tokens/revocable_refresh_token.rb
293
295
  - lib/doorkeeper/secret_storing/base.rb
294
296
  - lib/doorkeeper/secret_storing/bcrypt.rb
295
297
  - lib/doorkeeper/secret_storing/plain.rb
@@ -326,6 +328,7 @@ metadata:
326
328
  source_code_uri: https://github.com/doorkeeper-gem/doorkeeper
327
329
  bug_tracker_uri: https://github.com/doorkeeper-gem/doorkeeper/issues
328
330
  documentation_uri: https://doorkeeper.gitbook.io/guides/
331
+ funding_uri: https://opencollective.com/doorkeeper-gem
329
332
  post_install_message: "Starting from 5.5.0 RC1 Doorkeeper requires client authentication
330
333
  for Resource Owner Password Grant\nas stated in the OAuth RFC. You have to create
331
334
  a new OAuth client (Doorkeeper::Application) if you didn't\nhave it before and use