doorkeeper 6.0.0.beta2 → 6.0.0.rc2
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 +4 -4
- data/CHANGELOG.md +48 -2
- data/README.md +29 -0
- data/app/controllers/doorkeeper/authorizations_controller.rb +66 -3
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +23 -0
- data/app/controllers/doorkeeper/token_info_controller.rb +13 -1
- data/config/locales/en.yml +1 -0
- data/lib/doorkeeper/config/validations.rb +91 -0
- data/lib/doorkeeper/config.rb +18 -2
- data/lib/doorkeeper/errors.rb +14 -0
- data/lib/doorkeeper/grape/helpers.rb +11 -1
- data/lib/doorkeeper/helpers/controller.rb +12 -0
- data/lib/doorkeeper/http_fetcher.rb +79 -19
- data/lib/doorkeeper/models/access_token_mixin.rb +120 -2
- data/lib/doorkeeper/models/concerns/secret_storable.rb +72 -6
- data/lib/doorkeeper/oauth/authorization/token.rb +87 -8
- data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +13 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb +27 -20
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +17 -3
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +14 -0
- data/lib/doorkeeper/oauth/metadata_response.rb +4 -3
- data/lib/doorkeeper/oauth/pre_authorization.rb +50 -0
- data/lib/doorkeeper/oauth/refresh_token_request.rb +81 -24
- data/lib/doorkeeper/oauth/resource_indicator_validator.rb +10 -0
- data/lib/doorkeeper/oauth/token.rb +94 -2
- data/lib/doorkeeper/oauth/token_introspection.rb +14 -1
- data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +1 -0
- data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +1 -0
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +1 -0
- data/lib/doorkeeper/orm/active_record/mixins/secret_storable.rb +113 -0
- data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +5 -1
- data/lib/doorkeeper/orm/active_record.rb +1 -0
- data/lib/doorkeeper/rails/helpers.rb +25 -2
- data/lib/doorkeeper/request.rb +19 -7
- data/lib/doorkeeper/version.rb +1 -1
- data/lib/doorkeeper.rb +11 -0
- data/lib/generators/doorkeeper/refresh_token_scopes_generator.rb +43 -0
- data/lib/generators/doorkeeper/templates/add_refresh_token_scopes_to_access_tokens.rb.erb +10 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +94 -15
- data/lib/generators/doorkeeper/templates/migration.rb.erb +9 -0
- metadata +12 -12
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6d4350f4bd6db700eb44e19c8078618b64e440bfd1edf8ec51ef306ec1a99a9
|
|
4
|
+
data.tar.gz: a284bba8ee06ab1bf53ca43fd8a803320c4d5fe1b2576f24633f3119b23582b0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 33c85db50f78fbef731514c037cda2fbbe40b3b7c3c25e671563781e946c02aed6084ff0a2cc5bae0409e7e2808ab4f30d67639f3274b039ac39cc27a8144e3f
|
|
7
|
+
data.tar.gz: 633fbee3c0259e194a1b183f3c959ef90194d721755bcad84ccf0d198173f94d88aed428b615aed3e3c3d83d2b8c01f2b6f317d036f074aa9b0c699180a2e2a8
|
data/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,45 @@ User-visible changes worth mentioning.
|
|
|
9
9
|
|
|
10
10
|
- [#PR ID] Description of the change.
|
|
11
11
|
|
|
12
|
+
## 6.0.0.rc2
|
|
13
|
+
|
|
14
|
+
**Please make sure** you read the [Upgrade guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
|
|
15
|
+
and changelog below before the update since this version includes breaking changes.
|
|
16
|
+
|
|
17
|
+
- [#1951] Fix: the built gem no longer contains `vendor/bundle`. The gemspec globed all of `vendor/`, which swept in the bundle installed by the release workflow; 6.0.0.rc1 is a 44.4 MB download against 154 KB for 6.0.0.beta2.
|
|
18
|
+
- 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.
|
|
19
|
+
- 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.
|
|
20
|
+
- 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.
|
|
21
|
+
- [#1932] Fix: keep the scope originally granted by the resource owner on refresh tokens (RFC 6749 §6), so a chain narrowed on one refresh can return to its granted scope. Tracked in a new `refresh_token_scopes` column; existing installations opt in with `rails generate doorkeeper:refresh_token_scopes`.
|
|
22
|
+
- [#1933] Warn at boot when the `implicit` or `password` grant flow is enabled: both are deprecated by RFC 9700 (OAuth 2.0 Security BCP) and removed from OAuth 2.1, and may be removed in a future Doorkeeper release.
|
|
23
|
+
- [#1915] Fix: fetching a client's `jwks_uri` now falls back to the other addresses returned by DNS when the first one cannot be connected to.
|
|
24
|
+
- [#1934] The refresh_token grant now consults `custom_access_token_expires_in` (with `Doorkeeper::OAuth::REFRESH_TOKEN` as the context grant type) for the TTL of the refreshed access token. A callable that returns `nil` for this grant, or no callable at all, keeps inheriting the TTL of the token being refreshed as before. A callable that returns a value unconditionally now applies to refreshes as well.
|
|
25
|
+
- [#1935] Add opt-in `public_client_access_token_expires_in` configuration option: a ceiling for the lifetime of access tokens issued to public (non-confidential) clients by any grant, refresh_token included, as OAuth 2.1 Section 2.4 requires the exposure of tokens issued to unauthenticated clients to be limited. Confidential clients are not affected.
|
|
26
|
+
- [#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.
|
|
27
|
+
- [#1950] Document `hash_token_secrets` / `hash_application_secrets` `fallback:` as a migration-period setting that should be removed once every row is hashed, and warn at boot for as long as one is configured. While a `:plain` fallback is active the stored value is itself a valid credential, so those columns need protecting as carefully as plaintext ones.
|
|
28
|
+
- [#1953] Fix: `public_client_access_token_expires_in` now also holds under `reuse_access_token` and with String TTLs, and the refresh_token grant hands `custom_access_token_expires_in` and `resource_indicator_validator` what every other grant does.
|
|
29
|
+
|
|
30
|
+
## 6.0.0.rc1
|
|
31
|
+
|
|
32
|
+
**Please make sure** you read the [Upgrade guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
|
|
33
|
+
and changelog below before the update since this version includes breaking changes.
|
|
34
|
+
|
|
35
|
+
- Require Ruby >= 3.2 in the gemspec, matching the CI matrix (3.2 / 3.3 / 3.4 / 4.0). Ruby 2.7, 3.0 and 3.1 have reached end-of-life.
|
|
36
|
+
- Fix: the `client_secret_basic` strategy now requires a `client_id` sent in the request body to name the same client as the `Authorization: Basic` header — the RFC 7521 §4.2 agreement check `private_key_jwt` already applies to an assertion's issuer. A request presenting Basic credentials for one client and a `client_id` for another was authenticated as the Basic client, silently discarding the other identity. A bare `client_id` is not a client authentication method of its own, so the RFC 6749 §2.3 multiple-methods check does not (and should not) count it.
|
|
37
|
+
- [#1906] Internal: exempt `Doorkeeper::Config` from `Metrics/ClassLength` with a directive on the class itself instead of raising the cop's global ceiling, so adding a configuration option no longer trips the limit.
|
|
38
|
+
- [#1907] Fix: a `resource` parameter no longer produces a 500 at the authorization endpoint when `resource_indicator_validator` is configured without the `doorkeeper:resource_indicators` migration. Such a request is now answered with `server_error`, as the token endpoint already did, and the missing migration is warned about at boot.
|
|
39
|
+
- [#1909] Add Rails 8.1 to CI test matrix.
|
|
40
|
+
- [#1910] Add opt-in `validate_client_before_resource_owner_authentication` configuration option: the authorization endpoint validates `client_id` and `redirect_uri` before authenticating the resource owner, so users are not sent through login for a request that can only fail.
|
|
41
|
+
- [#1916] Fix broken Coveralls coverage reporting.
|
|
42
|
+
- [#1918] The api_only controller specs no longer `load` the real controller sources, which detached the coverage of every other example that ran them and made the reported coverage depend on the random example order.
|
|
43
|
+
- [#1923] Fix: the fallback secret upgrade no longer writes the matched secret back over a value stored in the meantime, which could undo a concurrent `#renew_secret` and leave the superseded secret valid. Active Record writes the upgrade conditionally on the column still holding the value that matched; other ORMs can implement the new `write_upgraded_secret` hook. The Active Record write is a single `update_all` statement, so model callbacks and validations no longer run on this upgrade (timestamps and optimistic locking are still maintained).
|
|
44
|
+
- [#1925] Internal: pin the development dependency on `json` below 3.0. json 3 removed the positional options Hash from `JSON.parse` and the `quirks_mode` option from `JSON.generate`, both of which Active Support still uses, so the suite could not run on any released Rails version.
|
|
45
|
+
- [#1926] **[BREAKING]** Fix: `private_key_jwt` client authentication no longer accepts an audience derived from the request's `Host` header, which let a client assertion minted for another authorization server be replayed here. A server that configures neither `issuer` nor Rails' `default_url_options[:host]` now has no acceptable audience and refuses every assertion, and is warned about it at boot.
|
|
46
|
+
- **[BREAKING]** Refuse requests that transmit the access token by more than one method (RFC 6750 §2) with an `invalid_request` error, instead of silently authorizing with the first method that yielded a token and discarding the rest. 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, and the same token repeated across two methods is refused too — §2 forbids the second method, not a disagreement between the two.
|
|
47
|
+
- Only the built-in extraction methods take part in the check; a custom callable in `access_token_methods` keeps the historical first-wins behavior and is never invoked more than once.
|
|
48
|
+
- `Doorkeeper::OAuth::Token.from_request` / `.authenticate` raise `Doorkeeper::Errors::MultipleAccessTokenMethods`. `Doorkeeper.authenticate` and every `doorkeeper_token` helper (Rails, Grape, and Doorkeeper's own controllers) keep their token-or-nil contract, so `doorkeeper_authorize!` renders the refusal through a new `doorkeeper_bad_request_render_options(error:)` hook (`head 400` unless you override it).
|
|
49
|
+
- **Upgrade note**: under `handle_auth_errors :raise` these requests raise `Doorkeeper::Errors::InvalidRequest`, a sibling of `Doorkeeper::Errors::InvalidToken` rather than a subclass — an existing `rescue Doorkeeper::Errors::InvalidToken` does not cover it.
|
|
50
|
+
|
|
12
51
|
## 6.0.0.beta2
|
|
13
52
|
|
|
14
53
|
**Please make sure** you read the [Upgrade guides](https://github.com/doorkeeper-gem/doorkeeper/wiki/Migration-from-old-versions)
|
|
@@ -37,7 +76,6 @@ and changelog below before the update since this version includes breaking chang
|
|
|
37
76
|
- [#1902] Fix: requests that omit `scope` now compute the same default scopes at the authorization and token endpoints (`Scopes#common`, symmetric). With dynamic scopes enabled, a scope pattern in either `default_scopes` or the application's scopes grants the matching concrete scope at both endpoints, closes [#1889].
|
|
38
77
|
- [#1903] Fix: `revoke_previous_client_credentials_token` no longer revokes a client's live access token issued for a different `resource` ([#1886]), so a client can keep one audience-restricted token per resource server.
|
|
39
78
|
- [#1905] [test] Cover `private_key_jwt` client authentication on the `client_credentials` grant, the one flow where an assertion is the client's only credential end to end. Test-only change.
|
|
40
|
-
- Please add here
|
|
41
79
|
|
|
42
80
|
## 6.0.0.beta1
|
|
43
81
|
|
|
@@ -78,6 +116,15 @@ and changelog below before the update since this version includes breaking chang
|
|
|
78
116
|
- [#1869] Improve test coverage
|
|
79
117
|
- [#1870] Fix: raise the intended `Doorkeeper::Errors::TokenGeneratorNotFound` / `UnableToGenerateToken` (instead of a confusing `NameError`) when `application_secret_generator` is misconfigured.
|
|
80
118
|
|
|
119
|
+
## 5.9.7
|
|
120
|
+
|
|
121
|
+
- 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.)
|
|
122
|
+
- [#1925] Internal: pin the development dependency on `json` below 3.0. json 3 removed the positional options Hash from `JSON.parse` and the `quirks_mode` option from `JSON.generate`, both of which Active Support still uses, so the suite could not run on any supported Rails version.
|
|
123
|
+
|
|
124
|
+
## 5.9.6
|
|
125
|
+
|
|
126
|
+
- Reject requests that present more than one client identity (e.g. an `Authorization: Basic` header for one client and a `client_id` parameter naming another) with an `invalid_request` error, instead of authenticating the first extracted identity and silently discarding the other one. A `client_id` sent alongside another authentication method keeps working when it identifies the same client (RFC 7521 §4.2). Like the RFC 6749 §2.3 check released in 5.9.5, this validation does not apply when `client_credentials` is configured with a callable extractor, since the credentials the remaining extractors would return are never evaluated — the `client_credentials` option documents that now.
|
|
127
|
+
|
|
81
128
|
## 5.9.5
|
|
82
129
|
|
|
83
130
|
- [#1901] Reject requests that authenticate the client with more than one method (RFC 6749 §2.3) with an `invalid_request` error, instead of silently authenticating with the first method that matched and discarding the other credentials.
|
|
@@ -132,7 +179,6 @@ and changelog below before the update since this version includes breaking chang
|
|
|
132
179
|
- [#1779] Only lock previous access token model when creating a new token from its refresh token if revoke_previous_refresh_token_on_use is false
|
|
133
180
|
- [#1778] Ensure that token revocation is idempotent by checking that that token has not already been revoked before revoking.
|
|
134
181
|
|
|
135
|
-
|
|
136
182
|
## 5.8.2
|
|
137
183
|
|
|
138
184
|
- [#1755] Fix the error message for force_pkce
|
data/README.md
CHANGED
|
@@ -39,7 +39,9 @@ Supported features:
|
|
|
39
39
|
- [Grape](#grape)
|
|
40
40
|
- [ORMs](#orms)
|
|
41
41
|
- [Extensions](#extensions)
|
|
42
|
+
- [Database maintenance](#database-maintenance)
|
|
42
43
|
- [Resource Indicators](#resource-indicators)
|
|
44
|
+
- [Refresh Token Scopes](#refresh-token-scopes)
|
|
43
45
|
- [Custom Grant Flows](#custom-grant-flows)
|
|
44
46
|
- [Custom Client Authentication Methods](#custom-client-authentication-methods)
|
|
45
47
|
- [Example Applications](#example-applications)
|
|
@@ -111,6 +113,18 @@ Extensions that are not included by default and can be installed separately.
|
|
|
111
113
|
| CIBA - Client Initiated Backchannel Authentication Flow extension | [doorkeeper-ciba](https://github.com/autoseg/doorkeeper-ciba) |
|
|
112
114
|
| Device Authorization Grant | [doorkeeper-device_authorization_grant](https://github.com/exop-group/doorkeeper-device_authorization_grant) |
|
|
113
115
|
|
|
116
|
+
## Database maintenance
|
|
117
|
+
|
|
118
|
+
Doorkeeper does **not** automatically remove expired or revoked tokens and grants. The `oauth_access_tokens` and `oauth_access_grants` tables grow indefinitely and can reach millions of rows if left unmanaged.
|
|
119
|
+
|
|
120
|
+
Prune them periodically with the bundled rake task:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
bundle exec rake doorkeeper:db:cleanup
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This deletes expired and revoked access tokens and grants. See the [Rake tasks guide](https://doorkeeper.gitbook.io/guides/internals/rake) for details.
|
|
127
|
+
|
|
114
128
|
## Resource Indicators
|
|
115
129
|
|
|
116
130
|
Doorkeeper supports [Resource Indicators for OAuth 2.0 (RFC 8707)](https://datatracker.ietf.org/doc/html/rfc8707), allowing clients to signal which protected resource(s) they intend to access. Tokens are then audience-restricted to those resources.
|
|
@@ -156,6 +170,21 @@ RFC 8707 uses repeated query parameters (`?resource=…&resource=…`) for multi
|
|
|
156
170
|
|
|
157
171
|
A single `resource=…` works as-is.
|
|
158
172
|
|
|
173
|
+
## Refresh Token Scopes
|
|
174
|
+
|
|
175
|
+
A client may narrow the access token it gets back from the refresh token grant by sending a `scope` parameter. Per [RFC 6749 §6](https://datatracker.ietf.org/doc/html/rfc6749#section-6) the refresh token itself keeps the scope originally granted by the resource owner: a later refresh that omits `scope` returns to the granted scope, and one that asks for the granted scope again is accepted.
|
|
176
|
+
|
|
177
|
+
Doorkeeper tracks the granted scope in the `refresh_token_scopes` column of `oauth_access_tokens`. New installs get it from the install migration; existing installs add it with:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
rails generate doorkeeper:refresh_token_scopes
|
|
181
|
+
rails db:migrate
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
With the column in place, [token introspection](https://datatracker.ietf.org/doc/html/rfc7662#section-2.2) of a refresh token reports the granted scope rather than the scope of the access token it was issued with, and `reuse_access_token` does not hand a grant of a narrower scope an existing token whose refresh token was granted a wider one.
|
|
185
|
+
|
|
186
|
+
Without the column, a narrowed refresh narrows the refresh token as well, so the chain can never return to the granted scope (the behavior of Doorkeeper before the column existed), and a `refresh_token_scopes` assigned to an access token is ignored. Rows created before the migration keep that behavior until they are rotated, and so do the [ORM extensions](#extensions) (Sequel, MongoDB) until they add the field.
|
|
187
|
+
|
|
159
188
|
## Custom Grant Flows
|
|
160
189
|
|
|
161
190
|
Besides the built-in OAuth 2 flows, Doorkeeper can recognize and process any custom grant type through its grant flow registry — including grant types whose names are URNs or URIs, such as the SAML 2.0 bearer assertion grant defined by [RFC 7522](https://www.rfc-editor.org/rfc/rfc7522).
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module Doorkeeper
|
|
4
4
|
class AuthorizationsController < Doorkeeper::ApplicationController
|
|
5
|
+
before_action :validate_client,
|
|
6
|
+
only: :new,
|
|
7
|
+
if: -> { Doorkeeper.config.validate_client_before_resource_owner_authentication? }
|
|
5
8
|
before_action :authenticate_resource_owner!
|
|
6
9
|
|
|
7
10
|
def new
|
|
@@ -16,7 +19,14 @@ module Doorkeeper
|
|
|
16
19
|
redirect_or_render(authorize_response)
|
|
17
20
|
end
|
|
18
21
|
|
|
22
|
+
# The deny path answers a request the client sent, so it is subject to the
|
|
23
|
+
# same client and redirect URI validation as #create: without it an
|
|
24
|
+
# unregistered redirect_uri was honoured and the user-agent redirected to
|
|
25
|
+
# it, carrying the OAuth `state` (and, since RFC 9207 support was added,
|
|
26
|
+
# the issuer) to an origin of the caller's choosing.
|
|
19
27
|
def destroy
|
|
28
|
+
return if refuse_invalid_client?(pre_auth)
|
|
29
|
+
|
|
20
30
|
redirect_or_render(authorization.deny)
|
|
21
31
|
rescue Doorkeeper::Errors::InvalidTokenStrategy => e
|
|
22
32
|
error_response = get_error_response_from_exception(e)
|
|
@@ -45,13 +55,66 @@ module Doorkeeper
|
|
|
45
55
|
|
|
46
56
|
if Doorkeeper.configuration.redirect_on_errors? && pre_auth.error_response.redirectable?
|
|
47
57
|
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
58
|
else
|
|
51
|
-
|
|
59
|
+
render_error_response(pre_auth.error_response)
|
|
52
60
|
end
|
|
53
61
|
end
|
|
54
62
|
|
|
63
|
+
def render_error_response(error_response)
|
|
64
|
+
if Doorkeeper.configuration.api_only
|
|
65
|
+
render json: error_response.body, status: error_response.status
|
|
66
|
+
else
|
|
67
|
+
render :error, locals: { error_response: error_response }, status: error_response.status
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Refuses the request before resource owner authentication when the
|
|
72
|
+
# client_id or redirect_uri is missing or invalid.
|
|
73
|
+
#
|
|
74
|
+
# Host applications that need to refuse a client on their own terms (an
|
|
75
|
+
# allow-list, a registry lookup) can override this and call +super+ first:
|
|
76
|
+
#
|
|
77
|
+
# class AuthorizationsController < Doorkeeper::AuthorizationsController
|
|
78
|
+
# private
|
|
79
|
+
#
|
|
80
|
+
# def validate_client
|
|
81
|
+
# super
|
|
82
|
+
# return if performed?
|
|
83
|
+
# return if MyRegistry.allowed?(client_pre_auth.client.application)
|
|
84
|
+
#
|
|
85
|
+
# head :forbidden
|
|
86
|
+
# end
|
|
87
|
+
# end
|
|
88
|
+
def validate_client
|
|
89
|
+
refuse_invalid_client?(client_pre_auth)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Renders the error of +pre_authorization+ when its client_id or
|
|
93
|
+
# redirect_uri is missing or invalid, and answers whether the request was
|
|
94
|
+
# refused. Errors are rendered, never redirected, regardless of
|
|
95
|
+
# `handle_auth_errors :redirect`: no failure this check can produce leaves
|
|
96
|
+
# a redirect target worth trusting. Either the client failed first, so the
|
|
97
|
+
# redirect URI was never reached, or the redirect URI is itself the invalid
|
|
98
|
+
# one — and RFC 6749 Section 3.1.2.4 forbids redirecting the user-agent to
|
|
99
|
+
# an invalid redirection URI.
|
|
100
|
+
def refuse_invalid_client?(pre_authorization)
|
|
101
|
+
return false if pre_authorization.client_valid?
|
|
102
|
+
|
|
103
|
+
error_response = pre_authorization.error_response
|
|
104
|
+
error_response.raise_exception! if Doorkeeper.config.raise_on_errors?
|
|
105
|
+
|
|
106
|
+
render_error_response(error_response)
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The pre-authentication view of the request. It carries no resource owner
|
|
111
|
+
# (nobody is authenticated yet), so it is kept apart from #pre_auth:
|
|
112
|
+
# owner-dependent validations (authorize_resource_owner_for_client) and the
|
|
113
|
+
# views need the one built with current_resource_owner.
|
|
114
|
+
def client_pre_auth
|
|
115
|
+
@client_pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration, pre_auth_params)
|
|
116
|
+
end
|
|
117
|
+
|
|
55
118
|
def can_authorize_response?
|
|
56
119
|
Doorkeeper.config.custom_access_token_attributes.empty? && pre_auth.client.application.confidential? && matching_token?
|
|
57
120
|
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
|
|
@@ -6,7 +6,7 @@ module Doorkeeper
|
|
|
6
6
|
if doorkeeper_token&.accessible?
|
|
7
7
|
render json: doorkeeper_token_to_json, status: :ok
|
|
8
8
|
else
|
|
9
|
-
error =
|
|
9
|
+
error = doorkeeper_token_error_response
|
|
10
10
|
response.headers.merge!(error.headers)
|
|
11
11
|
render json: error_to_json(error), status: error.status
|
|
12
12
|
end
|
|
@@ -21,5 +21,17 @@ module Doorkeeper
|
|
|
21
21
|
def error_to_json(error)
|
|
22
22
|
error.body
|
|
23
23
|
end
|
|
24
|
+
|
|
25
|
+
# RFC 6750 §3.1: transmitting the access token by more than one method is
|
|
26
|
+
# answered with invalid_request (400), not invalid_token (401). The helper
|
|
27
|
+
# answers nil for such a request and keeps the error, so the response is
|
|
28
|
+
# chosen here rather than by rescuing a raise.
|
|
29
|
+
def doorkeeper_token_error_response
|
|
30
|
+
if (error = @_doorkeeper_multiple_token_methods_error)
|
|
31
|
+
OAuth::InvalidRequestResponse.new(reason: error.reason)
|
|
32
|
+
else
|
|
33
|
+
OAuth::InvalidTokenResponse.new
|
|
34
|
+
end
|
|
35
|
+
end
|
|
24
36
|
end
|
|
25
37
|
end
|
data/config/locales/en.yml
CHANGED
|
@@ -99,6 +99,7 @@ en:
|
|
|
99
99
|
request_not_authorized: 'Request needs to be authorized. Required parameter for authorizing the request is missing or invalid.'
|
|
100
100
|
invalid_code_challenge: 'Code challenge is required.'
|
|
101
101
|
multiple_client_auth_methods: 'The request utilizes more than one mechanism for authenticating the client.'
|
|
102
|
+
multiple_access_token_methods: 'The request uses more than one method for including an access token.'
|
|
102
103
|
invalid_redirect_uri: "The requested redirect URI is malformed or doesn't match the client redirect URI."
|
|
103
104
|
unauthorized_client: 'The client is not authorized to perform this request using this method.'
|
|
104
105
|
access_denied: 'The resource owner or authorization server denied the request.'
|
|
@@ -10,12 +10,15 @@ module Doorkeeper
|
|
|
10
10
|
def validate!
|
|
11
11
|
validate_client_authentication_conflict
|
|
12
12
|
validate_client_authentication_registered
|
|
13
|
+
validate_private_key_jwt_identity
|
|
13
14
|
validate_reuse_access_token_value
|
|
14
15
|
validate_token_reuse_limit
|
|
15
16
|
validate_secret_strategies
|
|
17
|
+
validate_secret_fallback_strategies
|
|
16
18
|
validate_pkce_code_challenge_methods
|
|
17
19
|
validate_custom_metadata
|
|
18
20
|
validate_refresh_token_flow
|
|
21
|
+
validate_deprecated_grant_flows
|
|
19
22
|
validate_issuer_format
|
|
20
23
|
validate_issuer_metadata_discoverability
|
|
21
24
|
end
|
|
@@ -70,6 +73,33 @@ module Doorkeeper
|
|
|
70
73
|
)
|
|
71
74
|
end
|
|
72
75
|
|
|
76
|
+
# Warn when private_key_jwt client authentication is enabled on a server
|
|
77
|
+
# that identifies itself nowhere. An assertion's audience is what keeps
|
|
78
|
+
# it from being replayed at another authorization server, so it is only
|
|
79
|
+
# ever checked against the configured +issuer+ or Rails'
|
|
80
|
+
# +default_url_options+ - never against the request's Host header, which
|
|
81
|
+
# the client controls. With neither configured there is no acceptable
|
|
82
|
+
# audience and every assertion is refused, so make that loud at boot
|
|
83
|
+
# rather than leave it to be discovered request by request.
|
|
84
|
+
def validate_private_key_jwt_identity
|
|
85
|
+
# The deprecated client_credentials option only maps onto shared-secret
|
|
86
|
+
# methods, so a configuration using it alone never enables this one.
|
|
87
|
+
return if instance_variable_defined?(:@client_credentials_methods) &&
|
|
88
|
+
!instance_variable_defined?(:@client_authentication)
|
|
89
|
+
return unless client_authentication.map(&:to_s).include?("private_key_jwt")
|
|
90
|
+
return if issuer.present?
|
|
91
|
+
|
|
92
|
+
url_options = ::Rails.application&.routes&.default_url_options || {}
|
|
93
|
+
return if url_options[:host].present?
|
|
94
|
+
|
|
95
|
+
::Rails.logger.error(
|
|
96
|
+
"[DOORKEEPER] private_key_jwt client authentication is enabled, but the server " \
|
|
97
|
+
"identifies itself nowhere, so no audience is acceptable and every client " \
|
|
98
|
+
"assertion will be refused. Configure issuer, or " \
|
|
99
|
+
"Rails.application.routes.default_url_options[:host].",
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
73
103
|
# Determine whether +reuse_access_token+ and a non-restorable
|
|
74
104
|
# +token_secret_strategy+ have both been activated.
|
|
75
105
|
#
|
|
@@ -93,6 +123,46 @@ module Doorkeeper
|
|
|
93
123
|
application_secret_strategy.validate_for(:application)
|
|
94
124
|
end
|
|
95
125
|
|
|
126
|
+
# Warn for as long as a secret fallback strategy is configured. The
|
|
127
|
+
# fallback exists so that values written before hashing was enabled stay
|
|
128
|
+
# valid while they migrate, which makes it a setting for the migration
|
|
129
|
+
# period rather than a permanent one - but nothing expires it, and an
|
|
130
|
+
# installation that finished migrating long ago keeps it silently.
|
|
131
|
+
#
|
|
132
|
+
# A +:plain+ fallback is worth saying more about: it looks a value up by
|
|
133
|
+
# the value exactly as given, so while it is configured whatever the
|
|
134
|
+
# credential column holds is itself a valid credential. Against anyone
|
|
135
|
+
# who can read the column - a dump, a read-only injection, a logged query
|
|
136
|
+
# - the hashing protects nothing until the fallback is removed. A
|
|
137
|
+
# fallback to another hashing strategy does not have that property, since
|
|
138
|
+
# its stored values are still transformed, so it only gets the first part
|
|
139
|
+
# of the warning.
|
|
140
|
+
#
|
|
141
|
+
# The warning is unconditional rather than raised only while unhashed
|
|
142
|
+
# rows remain: answering that question means querying the credential
|
|
143
|
+
# tables at boot, and the setting itself is what needs surfacing.
|
|
144
|
+
def validate_secret_fallback_strategies
|
|
145
|
+
{
|
|
146
|
+
token: token_secret_fallback_strategy,
|
|
147
|
+
application: application_secret_fallback_strategy,
|
|
148
|
+
}.each do |type, strategy|
|
|
149
|
+
next if strategy.nil?
|
|
150
|
+
|
|
151
|
+
message =
|
|
152
|
+
"[DOORKEEPER] hash_#{type}_secrets is configured with a fallback strategy " \
|
|
153
|
+
"(#{strategy}), which is intended for the migration period only. Remove the " \
|
|
154
|
+
"fallback: option once every row has been migrated to the hashed format."
|
|
155
|
+
|
|
156
|
+
if strategy <= Doorkeeper::SecretStoring::Plain
|
|
157
|
+
message += " While it is configured, a value stored in the #{type} credential " \
|
|
158
|
+
"column is itself a valid credential, so that column needs to be " \
|
|
159
|
+
"protected as carefully as a plaintext credential."
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
::Rails.logger.warn(message)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
96
166
|
def validate_token_reuse_limit
|
|
97
167
|
return if !reuse_access_token ||
|
|
98
168
|
(token_reuse_limit > 0 && token_reuse_limit <= 100)
|
|
@@ -157,6 +227,27 @@ module Doorkeeper
|
|
|
157
227
|
)
|
|
158
228
|
end
|
|
159
229
|
|
|
230
|
+
# Grant flows deprecated by RFC 9700 (OAuth 2.0 Security Best Current
|
|
231
|
+
# Practice, BCP 240) and removed from OAuth 2.1: the implicit grant
|
|
232
|
+
# (RFC 9700 §2.1.2) and the resource owner password credentials grant
|
|
233
|
+
# (RFC 9700 §2.4). Warn at boot when one of them is enabled so that
|
|
234
|
+
# applications relying on them notice ahead of the removal.
|
|
235
|
+
DEPRECATED_GRANT_FLOWS = %w[implicit password].freeze
|
|
236
|
+
|
|
237
|
+
def validate_deprecated_grant_flows
|
|
238
|
+
enabled = calculate_grant_flows.map(&:to_s)
|
|
239
|
+
|
|
240
|
+
DEPRECATED_GRANT_FLOWS.each do |flow|
|
|
241
|
+
next unless enabled.include?(flow)
|
|
242
|
+
|
|
243
|
+
::Rails.logger.warn(
|
|
244
|
+
"[DOORKEEPER] The #{flow} grant flow is deprecated by RFC 9700 (OAuth 2.0 " \
|
|
245
|
+
"Security Best Current Practice) and removed from OAuth 2.1. It is enabled " \
|
|
246
|
+
"via grant_flows and may be removed in a future Doorkeeper release.",
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
160
251
|
# Warn when a configured issuer is not RFC-compliant. RFC 8414 (the
|
|
161
252
|
# metadata issuer) and RFC 9207 (the authorization response iss parameter)
|
|
162
253
|
# both require an https URL with a host and no query or fragment component.
|
data/lib/doorkeeper/config.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Doorkeeper
|
|
|
10
10
|
# that returns configuration Builder class. This exception raises when they don't
|
|
11
11
|
# define it.
|
|
12
12
|
#
|
|
13
|
-
class Config
|
|
13
|
+
class Config # rubocop:disable Metrics/ClassLength
|
|
14
14
|
# Default Doorkeeper configuration builder
|
|
15
15
|
class Builder < AbstractBuilder
|
|
16
16
|
# Provide support for an owner to be assigned to each registered
|
|
@@ -182,6 +182,13 @@ module Doorkeeper
|
|
|
182
182
|
@config.instance_variable_set(:@force_pkce, true)
|
|
183
183
|
end
|
|
184
184
|
|
|
185
|
+
# Validate the authorization request's client_id and redirect_uri before
|
|
186
|
+
# authenticating the resource owner, so users are not sent through login
|
|
187
|
+
# for a request that can only fail (disabled by default)
|
|
188
|
+
def validate_client_before_resource_owner_authentication
|
|
189
|
+
@config.instance_variable_set(:@validate_client_before_resource_owner_authentication, true)
|
|
190
|
+
end
|
|
191
|
+
|
|
185
192
|
# Use an API mode for applications generated with --api argument
|
|
186
193
|
# It will skip applications controller, disable forgery protection
|
|
187
194
|
def api_only
|
|
@@ -309,6 +316,9 @@ module Doorkeeper
|
|
|
309
316
|
option :skip_authorization, default: ->(_routes) {}
|
|
310
317
|
option :access_token_expires_in, default: 7200
|
|
311
318
|
option :custom_access_token_expires_in, default: ->(_context) { nil }
|
|
319
|
+
# Ceiling for the TTL of access tokens issued to public clients (OAuth 2.1 Section 2.4).
|
|
320
|
+
# Applies to every grant, including refresh_token. nil (the default) means no ceiling.
|
|
321
|
+
option :public_client_access_token_expires_in, default: nil
|
|
312
322
|
option :authorization_code_expires_in, default: 600
|
|
313
323
|
option :orm, default: :active_record
|
|
314
324
|
option :native_redirect_uri, default: "urn:ietf:wg:oauth:2.0:oob", deprecated: true
|
|
@@ -369,7 +379,9 @@ module Doorkeeper
|
|
|
369
379
|
option :allow_grant_flow_for_client, default: ->(_grant_flow, _client) { true }
|
|
370
380
|
|
|
371
381
|
# Allows to forbid specific Application redirect URI's by custom rules.
|
|
372
|
-
# Doesn't forbid any URI by default.
|
|
382
|
+
# Doesn't forbid any URI by default. Redirect URIs with a script scheme
|
|
383
|
+
# (`javascript`, `vbscript`, `data`) are always rejected, regardless of
|
|
384
|
+
# this option.
|
|
373
385
|
#
|
|
374
386
|
# @param forbid_redirect_uri [Proc] Block or any object respond to #call
|
|
375
387
|
#
|
|
@@ -629,6 +641,10 @@ module Doorkeeper
|
|
|
629
641
|
option_set? :force_pkce
|
|
630
642
|
end
|
|
631
643
|
|
|
644
|
+
def validate_client_before_resource_owner_authentication?
|
|
645
|
+
option_set? :validate_client_before_resource_owner_authentication
|
|
646
|
+
end
|
|
647
|
+
|
|
632
648
|
def enforce_configured_scopes?
|
|
633
649
|
option_set? :enforce_configured_scopes
|
|
634
650
|
end
|
data/lib/doorkeeper/errors.rb
CHANGED
|
@@ -37,6 +37,20 @@ module Doorkeeper
|
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Raised when a request transmits an access token by more than one method,
|
|
41
|
+
# which RFC 6750 §2 explicitly forbids ("Clients MUST NOT use more than
|
|
42
|
+
# one method to transmit the token in each request"); §3.1 lists it among
|
|
43
|
+
# the conditions answered with invalid_request (HTTP 400).
|
|
44
|
+
class MultipleAccessTokenMethods < DoorkeeperError
|
|
45
|
+
def type
|
|
46
|
+
:invalid_request
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def reason
|
|
50
|
+
:multiple_access_token_methods
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
40
54
|
class MissingRequiredParameter < DoorkeeperError
|
|
41
55
|
attr_reader :missing_param
|
|
42
56
|
|
|
@@ -36,11 +36,20 @@ module Doorkeeper
|
|
|
36
36
|
env["api.endpoint"]
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# Memoized with the same defined? guard as Rails::Helpers#doorkeeper_token:
|
|
40
|
+
# both a request without a usable token and a refused multi-method request
|
|
41
|
+
# leave @doorkeeper_token nil, which ||= would re-evaluate on every call —
|
|
42
|
+
# re-running authentication just to reach the same verdict again.
|
|
39
43
|
def doorkeeper_token
|
|
40
|
-
@doorkeeper_token
|
|
44
|
+
return @doorkeeper_token if defined?(@doorkeeper_token)
|
|
45
|
+
|
|
46
|
+
@doorkeeper_token = OAuth::Token.authenticate(
|
|
41
47
|
decorated_request,
|
|
42
48
|
*Doorkeeper.config.access_token_methods,
|
|
43
49
|
)
|
|
50
|
+
rescue Errors::MultipleAccessTokenMethods => e
|
|
51
|
+
@_doorkeeper_multiple_token_methods_error = e
|
|
52
|
+
@doorkeeper_token = nil
|
|
44
53
|
end
|
|
45
54
|
|
|
46
55
|
def decorated_request
|
|
@@ -49,6 +58,7 @@ module Doorkeeper
|
|
|
49
58
|
|
|
50
59
|
def error_status_codes
|
|
51
60
|
{
|
|
61
|
+
bad_request: 400,
|
|
52
62
|
unauthorized: 401,
|
|
53
63
|
forbidden: 403,
|
|
54
64
|
}
|
|
@@ -39,10 +39,22 @@ module Doorkeeper
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# :doc:
|
|
42
|
+
#
|
|
43
|
+
# Answers nil (not a raise) when the request transmits an access token
|
|
44
|
+
# by more than one method (RFC 6750 §2), the same token-or-nil contract
|
|
45
|
+
# Rails::Helpers#doorkeeper_token keeps. This is the doorkeeper_token a
|
|
46
|
+
# subclass of Doorkeeper::ApplicationController or
|
|
47
|
+
# Doorkeeper::ApplicationMetalController resolves to, so a raise here
|
|
48
|
+
# would turn doorkeeper_authorize! in such a controller into an
|
|
49
|
+
# unhandled 500. The error is kept so the response can still be the
|
|
50
|
+
# invalid_request (400) RFC 6750 §3.1 prescribes.
|
|
42
51
|
def doorkeeper_token
|
|
43
52
|
return @doorkeeper_token if defined?(@doorkeeper_token)
|
|
44
53
|
|
|
45
54
|
@doorkeeper_token ||= OAuth::Token.authenticate(request, *config_methods)
|
|
55
|
+
rescue Errors::MultipleAccessTokenMethods => e
|
|
56
|
+
@_doorkeeper_multiple_token_methods_error = e
|
|
57
|
+
@doorkeeper_token = nil
|
|
46
58
|
end
|
|
47
59
|
|
|
48
60
|
def config_methods
|