doorkeeper 5.9.7 → 5.9.9

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: b21e3bb3454e1896aa0a0d022741de322d5cf604de408dfe58a1a4ef2a21cfbc
4
- data.tar.gz: 9384048264886cad0506a879964635253fb00a4903a6347e7801c066c6f971d3
3
+ metadata.gz: 3c4d0c4d775062d003452c97290c5fb4308812e17590946b9f8db9922e40f9d7
4
+ data.tar.gz: 7af434e11413a59e94cf12edd6ddcb088940110fabbd1174f0bacb2869a254da
5
5
  SHA512:
6
- metadata.gz: 18f0100ea947793a51547ee3de5ffd0927b8c4a6e1a9b491448c4149927ec34521b4d28c45056c5ca0d4b6dd87f79b796167e463f0a4cb1830eb2bf5732fc9b8
7
- data.tar.gz: 16c2996fa91d47283fcbf491773d7f2ed4d0e14b8faf0566f6757875de1b31af3ac058e19a77487c74ec13ca6dfde6a619048484b73f5a4bccc9814441a24122
6
+ metadata.gz: 1029aa2d0ff981b3d114f656f5b8d17308a946edd9466e7fe93d93fa5b6d1ba3c6873ba6cf628eab86ccb2f50b14507f272d61a0af829f2184963908941bb91c
7
+ data.tar.gz: 5b474f0a07ba2785acdbadca5d874a0b9e50955bebd0318499f84710cb06b0d1e8d58419ac4b889045e504011ddc88398ffea0c345434df291f0b43cf60fb3d2
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ upgrade guides.
5
5
 
6
6
  User-visible changes worth mentioning.
7
7
 
8
+ ## 5.9.9
9
+
10
+ - Fix: `AuthorizedApplicationsController` now answers `401 Unauthorized` instead of running with a `nil` resource owner, which listed and revoked every token that has no resource owner — the ones the client credentials flow issues. Affected host applications are those whose `resource_owner_authenticator` answers `nil` without halting the request itself; the generated initializer's example redirects and is not affected.
11
+ - Fix: `AuthorizationsController#destroy` now validates the client and redirect URI before producing the deny response, and renders — never redirects — when validation fails. Previously the deny path performed no OAuth-layer validation at all, allowing an open redirect to an attacker-controlled origin with the OAuth `state` attached. Also reject unregistered `response_type` values on the authorization endpoint rather than resolving them through the `constantize` fallback.
12
+ - Fix: refuse redirect URIs with a script scheme (`javascript`, `vbscript`, `data`) both when an application is registered and at authorization time, regardless of `forbid_redirect_uri`. Such a URI is never a legitimate redirection endpoint, and with `response_mode=form_post` it became the action of the auto-submitting form the authorization server renders on its own origin. Already stored records with such a URI are now refused with `invalid_redirect_uri` before the consent screen is shown.
13
+
14
+ ## 5.9.8
15
+
16
+ - [#1938] Fix: a request body ActionDispatch cannot parse (malformed JSON under a JSON content type, say) no longer raises `ActionDispatch::Http::Parameters::ParseError` out of `Doorkeeper::OAuth::Token.from_request` and `doorkeeper_token`. Since 5.9.7 the RFC 6750 §2 multi-method check read the body on every request, so such a request raised even when it carried a valid Bearer header. The body is now treated as carrying no token, the same way ActionDispatch's own `#filtered_parameters` treats that error.
17
+
8
18
  ## 5.9.7
9
19
 
10
20
  - Refuse requests that transmit an access token by more than one method (RFC 6750 §2), instead of silently authorizing with the first configured `access_token_methods` entry that matched and discarding the other tokens. Such a request now fails closed as carrying no usable token (401 `invalid_token`); no calling contract changes. The form-encoded body (§2.2) and the URI query (§2.3) count as two methods even though Rails and Rack merge them into a single `params` hash. The same token repeated across two methods is refused too — §2 forbids the second method, not a disagreement between the two — and a custom callable extractor in `access_token_methods` keeps the historical first-wins behavior and is never invoked more than once. (The strict `invalid_request` (400) answer §3.1 prescribes ships with Doorkeeper 6.0.)
@@ -16,7 +16,13 @@ module Doorkeeper
16
16
  redirect_or_render(authorize_response)
17
17
  end
18
18
 
19
+ # The deny path answers a request the client sent, so it is subject to the
20
+ # same client and redirect URI validation as #create: without it an
21
+ # unregistered redirect_uri was honoured and the user-agent redirected to
22
+ # it, carrying the OAuth `state` to an origin of the caller's choosing.
19
23
  def destroy
24
+ return if refuse_invalid_client?
25
+
20
26
  redirect_or_render(authorization.deny)
21
27
  rescue Doorkeeper::Errors::InvalidTokenStrategy => e
22
28
  error_response = get_error_response_from_exception(e)
@@ -45,13 +51,37 @@ module Doorkeeper
45
51
 
46
52
  if Doorkeeper.configuration.redirect_on_errors? && pre_auth.error_response.redirectable?
47
53
  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
50
54
  else
51
- render :error, locals: { error_response: pre_auth.error_response }, status: pre_auth.error_response.status
55
+ render_error_response(pre_auth.error_response)
52
56
  end
53
57
  end
54
58
 
59
+ def render_error_response(error_response)
60
+ if Doorkeeper.configuration.api_only
61
+ render json: error_response.body, status: error_response.status
62
+ else
63
+ render :error, locals: { error_response: error_response }, status: error_response.status
64
+ end
65
+ end
66
+
67
+ # Renders the error when the request's client_id or redirect_uri is missing
68
+ # or invalid, and answers whether the request was refused. Errors are
69
+ # rendered, never redirected, regardless of `handle_auth_errors :redirect`:
70
+ # no failure this check can produce leaves a redirect target worth
71
+ # trusting. Either the client failed first, so the redirect URI was never
72
+ # reached, or the redirect URI is itself the invalid one - and RFC 6749
73
+ # Section 3.1.2.4 forbids redirecting the user-agent to an invalid
74
+ # redirection URI.
75
+ def refuse_invalid_client?
76
+ return false if pre_auth.client_valid?
77
+
78
+ error_response = pre_auth.error_response
79
+ error_response.raise_exception! if Doorkeeper.config.raise_on_errors?
80
+
81
+ render_error_response(error_response)
82
+ true
83
+ end
84
+
55
85
  def can_authorize_response?
56
86
  Doorkeeper.config.custom_access_token_attributes.empty? && pre_auth.client.application.confidential? && matching_token?
57
87
  end
@@ -3,6 +3,7 @@
3
3
  module Doorkeeper
4
4
  class AuthorizedApplicationsController < Doorkeeper::ApplicationController
5
5
  before_action :authenticate_resource_owner!
6
+ before_action :validate_resource_owner
6
7
 
7
8
  def index
8
9
  @applications = Doorkeeper.config.application_model.authorized_for(current_resource_owner)
@@ -29,5 +30,27 @@ module Doorkeeper
29
30
  format.json { head :no_content }
30
31
  end
31
32
  end
33
+
34
+ private
35
+
36
+ # `authenticate_resource_owner!` hands the request to the host
37
+ # application's `resource_owner_authenticator` block, which is expected to
38
+ # halt the request itself — redirect to a sign-in page, raise, render —
39
+ # when nobody is signed in. A block that merely answers nil halts nothing:
40
+ # the library default (no block configured) logs a warning and answers
41
+ # nil, and so does any block written as a bare lookup such as
42
+ # `User.find_by(id: session[:user_id])`.
43
+ #
44
+ # Both actions here scope their work to `current_resource_owner`, and a
45
+ # nil owner is not "no scope" — it is the scope of the records that have
46
+ # no resource owner, which is what the client credentials flow issues.
47
+ # #index would list every application holding one and #destroy would
48
+ # revoke them, for a caller that never authenticated. So refuse the
49
+ # request rather than treat "nobody" as an owner. Only this controller is
50
+ # guarded; `authenticate_resource_owner!` itself is left alone because
51
+ # AuthorizationsController shapes its own response around the owner.
52
+ def validate_resource_owner
53
+ head :unauthorized unless current_resource_owner
54
+ end
32
55
  end
33
56
  end
@@ -329,7 +329,9 @@ module Doorkeeper
329
329
  option :allow_grant_flow_for_client, default: ->(_grant_flow, _client) { true }
330
330
 
331
331
  # Allows to forbid specific Application redirect URI's by custom rules.
332
- # Doesn't forbid any URI by default.
332
+ # Doesn't forbid any URI by default. Redirect URIs with a script scheme
333
+ # (`javascript`, `vbscript`, `data`) are always rejected, regardless of
334
+ # this option.
333
335
  #
334
336
  # @param forbid_redirect_uri [Proc] Block or any object respond to #call
335
337
  #
@@ -6,6 +6,15 @@ module Doorkeeper
6
6
  module OAuth
7
7
  module Helpers
8
8
  module URIChecker
9
+ # Schemes a user agent evaluates in the document that navigates to
10
+ # them instead of fetching a resource from somewhere else. With
11
+ # `response_mode=form_post` that document is the authorization
12
+ # server's own page, so a redirect URI with one of these schemes would
13
+ # run the client's script on the server's origin. None of them can be
14
+ # a legitimate redirection endpoint (RFC 6749 Section 3.1.2), so they
15
+ # are refused regardless of the `forbid_redirect_uri` configuration.
16
+ SCRIPT_SCHEMES = %w[javascript vbscript data].freeze
17
+
9
18
  def self.valid?(url)
10
19
  return true if oob_uri?(url)
11
20
 
@@ -62,10 +71,15 @@ module Doorkeeper
62
71
 
63
72
  def self.valid_scheme?(uri)
64
73
  return false if uri.scheme.blank?
74
+ return false if script_scheme?(uri)
65
75
 
66
76
  %w[localhost].exclude?(uri.scheme)
67
77
  end
68
78
 
79
+ def self.script_scheme?(uri)
80
+ SCRIPT_SCHEMES.include?(uri.scheme.to_s.downcase)
81
+ end
82
+
69
83
  def self.hypertext_scheme?(uri)
70
84
  %w[http https].include?(uri.scheme)
71
85
  end
@@ -5,6 +5,14 @@ module Doorkeeper
5
5
  class PreAuthorization
6
6
  include Validations
7
7
 
8
+ # The validations that identify the client and its redirect URI. None of
9
+ # them depend on the resource owner, and none of their failures leaves a
10
+ # redirect target worth trusting: RFC 6749 Section 4.1.2.1 (Section
11
+ # 4.2.2.1 for the implicit flow) asks for the resource owner to be
12
+ # informed when the client_id is missing or invalid, and Section 3.1.2.4
13
+ # asks the same for the redirect URI.
14
+ CLIENT_VALIDATIONS = %i[client_id client redirect_uri].freeze
15
+
8
16
  validate :client_id, error: Errors::InvalidRequest
9
17
  validate :client, error: Errors::InvalidClient
10
18
  validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
@@ -40,6 +48,25 @@ module Doorkeeper
40
48
  valid?
41
49
  end
42
50
 
51
+ # Runs only CLIENT_VALIDATIONS, in declared order, so a request naming an
52
+ # unknown client or an invalid redirect URI can be refused on its own -
53
+ # the validations in between (client_supports_grant_flow,
54
+ # resource_owner_authorize_for_client) answer a different question and
55
+ # are left to a full #validate run.
56
+ def client_valid?
57
+ @error = nil
58
+ @missing_param = nil
59
+
60
+ self.class.validations.each do |validation|
61
+ next unless CLIENT_VALIDATIONS.include?(validation[:attribute])
62
+
63
+ @error = validation[:options][:error] unless send("validate_#{validation[:attribute]}")
64
+ break if @error
65
+ end
66
+
67
+ @error.nil?
68
+ end
69
+
43
70
  def scopes
44
71
  Scopes.from_string(scope)
45
72
  end
@@ -69,11 +69,11 @@ module Doorkeeper
69
69
  end
70
70
 
71
71
  def from_access_token_param(request)
72
- request.parameters[:access_token]
72
+ parameters(request)[:access_token]
73
73
  end
74
74
 
75
75
  def from_bearer_param(request)
76
- request.parameters[:bearer_token]
76
+ parameters(request)[:bearer_token]
77
77
  end
78
78
 
79
79
  def from_bearer_authorization(request)
@@ -114,12 +114,29 @@ module Doorkeeper
114
114
  return [] unless request.respond_to?(:GET) && request.respond_to?(:POST)
115
115
 
116
116
  query = request.GET
117
- body = request.POST
117
+ body = body_parameters(request)
118
118
  return [] unless query.is_a?(Hash) && body.is_a?(Hash)
119
119
 
120
120
  [query[parameter], body[parameter]].filter_map(&:presence)
121
121
  end
122
122
 
123
+ # A body ActionDispatch cannot parse — malformed JSON under a JSON
124
+ # content type, say — carries no token. Reading it must not raise out
125
+ # of the extractors, which host apps call from places that run after
126
+ # their own ParseError handling (instrumentation, exception apps), the
127
+ # same way ActionDispatch's own #filtered_parameters treats that error.
128
+ def parameters(request)
129
+ request.parameters
130
+ rescue ActionDispatch::Http::Parameters::ParseError
131
+ {}
132
+ end
133
+
134
+ def body_parameters(request)
135
+ request.POST
136
+ rescue ActionDispatch::Http::Parameters::ParseError
137
+ {}
138
+ end
139
+
123
140
  def token_from_basic_header(header, pattern)
124
141
  encoded_header = token_from_header(header, pattern)
125
142
  decode_basic_credentials_token(encoded_header)
@@ -34,8 +34,12 @@ module Doorkeeper
34
34
  Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri)
35
35
  end
36
36
 
37
+ # Script schemes are refused before the host application's rule is
38
+ # consulted, so a `forbid_redirect_uri` that rejects them too does not
39
+ # report the error twice.
37
40
  def forbidden_uri?(uri)
38
- Doorkeeper.config.forbid_redirect_uri.call(uri)
41
+ Doorkeeper::OAuth::Helpers::URIChecker.script_scheme?(uri) ||
42
+ Doorkeeper.config.forbid_redirect_uri.call(uri)
39
43
  end
40
44
 
41
45
  def unspecified_scheme?(uri)
@@ -3,18 +3,30 @@
3
3
  module Doorkeeper
4
4
  module Request
5
5
  class << self
6
+ # Resolves the strategy that answers the given response_type at the
7
+ # authorization endpoint.
8
+ #
9
+ # A response_type no registered authorization flow handles is refused
10
+ # rather than turned into a class name: `constantize` would otherwise
11
+ # resolve any constant under Doorkeeper::Request, token endpoint
12
+ # strategies (`password`, `client_credentials`, `refresh_token`,
13
+ # `authorization_code`) included, none of which can answer an
14
+ # authorization request.
6
15
  def authorization_strategy(response_type)
16
+ response_type = response_type.to_s
17
+
7
18
  grant_flow = authorization_flows.detect do |flow|
8
19
  flow.matches_response_type?(response_type)
9
20
  end
10
21
 
11
- if grant_flow
12
- grant_flow.response_type_strategy
13
- else
14
- # [NOTE]: this will be removed in a newer versions of Doorkeeper.
15
- # For retro-compatibility only
16
- build_fallback_strategy_class(response_type)
17
- end
22
+ raise Errors::InvalidTokenStrategy unless grant_flow
23
+
24
+ # [NOTE]: this will be removed in a newer versions of Doorkeeper.
25
+ # For retro-compatibility only: flows declared through the deprecated
26
+ # Config#calculate_authorization_response_types hook are built as
27
+ # GrantFlow::FallbackFlow and carry no strategy, so the class is looked
28
+ # up by name - but only for a response type the server does declare.
29
+ grant_flow.response_type_strategy || build_fallback_strategy_class(response_type)
18
30
  end
19
31
 
20
32
  def token_strategy(grant_type)
@@ -5,7 +5,7 @@ module Doorkeeper
5
5
  # Semantic versioning
6
6
  MAJOR = 5
7
7
  MINOR = 9
8
- TINY = 7
8
+ TINY = 9
9
9
  PRE = nil
10
10
 
11
11
  # Full version number
@@ -316,10 +316,13 @@ Doorkeeper.configure do
316
316
  # Specify what redirect URI's you want to block during Application creation.
317
317
  # Any redirect URI is allowed by default.
318
318
  #
319
- # You can use this option in order to forbid URI's with 'javascript' scheme
319
+ # Redirect URIs with a script scheme (`javascript`, `vbscript`, `data`) are
320
+ # always rejected, regardless of this option.
321
+ #
322
+ # You can use this option in order to forbid URI's of a particular host
320
323
  # for example.
321
324
  #
322
- # forbid_redirect_uri { |uri| uri.scheme.to_s.downcase == 'javascript' }
325
+ # forbid_redirect_uri { |uri| uri.host == 'example.com' }
323
326
 
324
327
  # Allows to set blank redirect URIs for Applications in case Doorkeeper configured
325
328
  # to use URI-less OAuth grant flows like Client Credentials or 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.9.7
4
+ version: 5.9.9
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: 2026-09-10 00:00:00.000000000 Z
14
+ date: 2026-09-24 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties