doorkeeper 5.9.3 → 6.0.0.beta1
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 +41 -0
- data/README.md +1 -0
- data/app/controllers/doorkeeper/metadata_controller.rb +20 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +10 -1
- data/app/views/doorkeeper/authorizations/new.html.erb +6 -0
- data/config/locales/en.yml +1 -0
- data/lib/doorkeeper/client_authentication/credentials.rb +11 -0
- data/lib/doorkeeper/client_authentication/fallback_method.rb +19 -0
- data/lib/doorkeeper/client_authentication/legacy_callable.rb +46 -0
- data/lib/doorkeeper/client_authentication/method.rb +23 -0
- data/lib/doorkeeper/client_authentication/registry.rb +47 -0
- data/lib/doorkeeper/client_authentication.rb +93 -0
- data/lib/doorkeeper/config/option.rb +1 -1
- data/lib/doorkeeper/config/validations.rb +162 -1
- data/lib/doorkeeper/config.rb +114 -9
- data/lib/doorkeeper/errors.rb +14 -0
- data/lib/doorkeeper/helpers/controller.rb +2 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +48 -8
- data/lib/doorkeeper/models/application_mixin.rb +14 -6
- data/lib/doorkeeper/models/concerns/secret_storable.rb +10 -1
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +11 -0
- data/lib/doorkeeper/oauth/authorization_code_request.rb +4 -1
- data/lib/doorkeeper/oauth/client.rb +18 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +53 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_post.rb +29 -0
- data/lib/doorkeeper/oauth/client_authentication/none.rb +32 -0
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +4 -1
- data/lib/doorkeeper/oauth/code_response.rb +16 -2
- data/lib/doorkeeper/oauth/error_response.rb +11 -2
- data/lib/doorkeeper/oauth/metadata_response.rb +157 -0
- data/lib/doorkeeper/oauth/pre_authorization.rb +12 -5
- data/lib/doorkeeper/oauth/token_introspection.rb +27 -7
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +2 -2
- data/lib/doorkeeper/rails/routes/mapping.rb +1 -0
- data/lib/doorkeeper/rails/routes.rb +6 -0
- data/lib/doorkeeper/request.rb +59 -0
- data/lib/doorkeeper/server.rb +5 -2
- data/lib/doorkeeper/version.rb +4 -4
- data/lib/doorkeeper.rb +8 -4
- data/lib/generators/doorkeeper/templates/initializer.rb +66 -6
- metadata +12 -2
- data/lib/doorkeeper/oauth/client/credentials.rb +0 -34
|
@@ -5,7 +5,7 @@ module Doorkeeper
|
|
|
5
5
|
class ErrorResponse < BaseResponse
|
|
6
6
|
include OAuth::Helpers
|
|
7
7
|
|
|
8
|
-
NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client
|
|
8
|
+
NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client].freeze
|
|
9
9
|
|
|
10
10
|
def self.from_request(request, attributes = {})
|
|
11
11
|
new(
|
|
@@ -38,6 +38,7 @@ module Doorkeeper
|
|
|
38
38
|
@exception_class = attributes[:exception_class]
|
|
39
39
|
@redirect_uri = attributes[:redirect_uri]
|
|
40
40
|
@response_on_fragment = attributes[:response_on_fragment]
|
|
41
|
+
@issuer = attributes[:issuer]
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def body
|
|
@@ -45,11 +46,19 @@ module Doorkeeper
|
|
|
45
46
|
error: name,
|
|
46
47
|
error_description: description,
|
|
47
48
|
state: state,
|
|
49
|
+
# RFC 9207 scopes the issuer to authorization error responses sent to
|
|
50
|
+
# the client, i.e. the ones redirected back to its redirect_uri. It is
|
|
51
|
+
# supplied only by the authorization endpoint (never token,
|
|
52
|
+
# introspection or protected resource), and gated on redirectable? so
|
|
53
|
+
# non-redirectable errors (invalid_client, invalid_redirect_uri) and
|
|
54
|
+
# out-of-band flows - which render to the user instead of redirecting
|
|
55
|
+
# to the client - do not carry it. Blank values are dropped below.
|
|
56
|
+
iss: (@issuer if redirectable?),
|
|
48
57
|
}.reject { |_, v| v.blank? }
|
|
49
58
|
end
|
|
50
59
|
|
|
51
60
|
def status
|
|
52
|
-
if name == :invalid_client
|
|
61
|
+
if name == :invalid_client
|
|
53
62
|
:unauthorized
|
|
54
63
|
else
|
|
55
64
|
:bad_request
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module OAuth
|
|
5
|
+
# OAuth 2.0 Authorization Server Metadata response as described in
|
|
6
|
+
# RFC 8414 (https://www.rfc-editor.org/rfc/rfc8414).
|
|
7
|
+
class MetadataResponse < BaseResponse
|
|
8
|
+
def initialize(base_url, url_builder)
|
|
9
|
+
super()
|
|
10
|
+
@base_url = base_url
|
|
11
|
+
@url_builder = url_builder
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def body
|
|
15
|
+
@body ||= begin
|
|
16
|
+
data = {
|
|
17
|
+
# presence: a blank issuer (e.g. an unset env var) must fall back
|
|
18
|
+
# to base_url, matching the issuer.present? condition below so the
|
|
19
|
+
# two fields cannot disagree.
|
|
20
|
+
issuer: issuer.presence || @base_url,
|
|
21
|
+
# Only advertise endpoints whose controllers are installed. A
|
|
22
|
+
# controller disabled through skip_controllers has no routes mapping,
|
|
23
|
+
# so endpoint_for resolves it to nil and it is dropped below.
|
|
24
|
+
authorization_endpoint: authorization_endpoint,
|
|
25
|
+
token_endpoint: token_endpoint,
|
|
26
|
+
revocation_endpoint: revocation_endpoint,
|
|
27
|
+
introspection_endpoint: (introspection_endpoint if introspection_enabled?),
|
|
28
|
+
scopes_supported: scopes_supported,
|
|
29
|
+
response_types_supported: response_types_supported,
|
|
30
|
+
response_modes_supported: response_modes_supported,
|
|
31
|
+
grant_types_supported: grant_types_supported,
|
|
32
|
+
token_endpoint_auth_methods_supported: token_endpoint_auth_methods_supported,
|
|
33
|
+
code_challenge_methods_supported: code_challenge_methods_supported,
|
|
34
|
+
# RFC 9207: true only when an issuer is configured, matching the
|
|
35
|
+
# condition under which the iss parameter is emitted. false survives
|
|
36
|
+
# the compaction below, so it is advertised explicitly.
|
|
37
|
+
authorization_response_iss_parameter_supported: config.issuer.present?,
|
|
38
|
+
}
|
|
39
|
+
data.compact!
|
|
40
|
+
|
|
41
|
+
# userinfo_endpoint is intentionally advertised as null for backwards
|
|
42
|
+
# compatibility; it is meant to be populated through custom_metadata
|
|
43
|
+
# (e.g. by an OIDC extension), so it must survive the compaction above.
|
|
44
|
+
data[:userinfo_endpoint] = userinfo_endpoint
|
|
45
|
+
|
|
46
|
+
data.merge(custom_metadata)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def status
|
|
51
|
+
:ok
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def headers
|
|
55
|
+
{
|
|
56
|
+
"Cache-Control" => "public",
|
|
57
|
+
"Content-Type" => "application/json; charset=utf-8",
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def config
|
|
64
|
+
@config ||= Doorkeeper.configuration
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def url_for(**args)
|
|
68
|
+
@url_builder.call(**args)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Resolve the URL for the given route group, or nil when the group has no
|
|
72
|
+
# routes mapping (i.e. the controller was disabled via skip_controllers).
|
|
73
|
+
def endpoint_for(group, action:)
|
|
74
|
+
mapping = Doorkeeper::Rails::Routes.mapping[group]
|
|
75
|
+
return unless mapping
|
|
76
|
+
|
|
77
|
+
# The leading slash anchors the controller name: without it, Rails
|
|
78
|
+
# resolves the name relative to the current (doorkeeper/metadata)
|
|
79
|
+
# namespace whenever their segment depths differ, e.g. a configured
|
|
80
|
+
# `controllers tokens: "custom_tokens"` would be looked up as
|
|
81
|
+
# "doorkeeper/custom_tokens" and raise ActionController::UrlGenerationError.
|
|
82
|
+
url_for(controller: "/#{mapping[:controllers]}", action: action)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def custom_metadata
|
|
86
|
+
config.custom_metadata.symbolize_keys
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def issuer
|
|
90
|
+
config.issuer
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def authorization_endpoint
|
|
94
|
+
endpoint_for(:authorizations, action: "new")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def token_endpoint
|
|
98
|
+
endpoint_for(:tokens, action: "create")
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def userinfo_endpoint
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def revocation_endpoint
|
|
106
|
+
endpoint_for(:tokens, action: "revoke")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def introspection_endpoint
|
|
110
|
+
endpoint_for(:tokens, action: "introspect")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def introspection_enabled?
|
|
114
|
+
Doorkeeper.configured? &&
|
|
115
|
+
!config.allow_token_introspection.is_a?(FalseClass)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def scopes_supported
|
|
119
|
+
config.scopes.to_a
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def response_types_supported
|
|
123
|
+
config.authorization_response_types
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def response_modes_supported
|
|
127
|
+
config.authorization_response_flows.flat_map(&:response_mode_matches).uniq
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def grant_types_supported
|
|
131
|
+
# RFC 8414 lists token-endpoint grant type values (e.g.
|
|
132
|
+
# "authorization_code", "refresh_token"), so derive them from the
|
|
133
|
+
# flows that actually handle a grant_type and use their grant_type
|
|
134
|
+
# value rather than the configured flow name. This drops
|
|
135
|
+
# response-type-only flows such as :implicit and reports the real
|
|
136
|
+
# grant type for flows whose name differs from it (e.g. a URN).
|
|
137
|
+
config.token_grant_types
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The resolved methods (rather than the raw client_authentication names)
|
|
141
|
+
# reflect what the server actually accepts: unregistered names are
|
|
142
|
+
# dropped by the resolver, and a deprecated client_credentials-only
|
|
143
|
+
# configuration is honored as the source of truth. Legacy callable
|
|
144
|
+
# extractors have no registered method name a client could use, so they
|
|
145
|
+
# are not advertised.
|
|
146
|
+
def token_endpoint_auth_methods_supported
|
|
147
|
+
config.client_authentication_methods.filter_map do |method|
|
|
148
|
+
method.name.to_s if Doorkeeper::ClientAuthentication.get(method.name)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def code_challenge_methods_supported
|
|
153
|
+
config.pkce_code_challenge_methods_supported
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -7,9 +7,9 @@ module Doorkeeper
|
|
|
7
7
|
|
|
8
8
|
validate :client_id, error: Errors::InvalidRequest
|
|
9
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
10
|
validate :redirect_uri, error: Errors::InvalidRedirectUri
|
|
11
|
+
validate :client_supports_grant_flow, error: Errors::UnauthorizedClient
|
|
12
|
+
validate :resource_owner_authorize_for_client, error: Errors::AccessDenied
|
|
13
13
|
validate :params, error: Errors::InvalidRequest
|
|
14
14
|
validate :response_type, error: Errors::UnsupportedResponseType
|
|
15
15
|
validate :response_mode, error: Errors::UnsupportedResponseMode
|
|
@@ -49,13 +49,21 @@ module Doorkeeper
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
def error_response
|
|
52
|
+
# RFC 9207: authorization error responses advertise the issuer. Passing
|
|
53
|
+
# the configured issuer here (nil when unset) scopes the iss parameter
|
|
54
|
+
# to the authorization endpoint only.
|
|
52
55
|
if error == Errors::InvalidRequest
|
|
53
56
|
OAuth::InvalidRequestResponse.from_request(
|
|
54
57
|
self,
|
|
55
58
|
response_on_fragment: response_on_fragment?,
|
|
59
|
+
issuer: Doorkeeper.config.issuer,
|
|
56
60
|
)
|
|
57
61
|
else
|
|
58
|
-
OAuth::ErrorResponse.from_request(
|
|
62
|
+
OAuth::ErrorResponse.from_request(
|
|
63
|
+
self,
|
|
64
|
+
response_on_fragment: response_on_fragment?,
|
|
65
|
+
issuer: Doorkeeper.config.issuer,
|
|
66
|
+
)
|
|
59
67
|
end
|
|
60
68
|
end
|
|
61
69
|
|
|
@@ -147,7 +155,6 @@ module Doorkeeper
|
|
|
147
155
|
|
|
148
156
|
def validate_code_challenge
|
|
149
157
|
return true unless Doorkeeper.config.force_pkce?
|
|
150
|
-
return true if client.confidential
|
|
151
158
|
return true if code_challenge.present?
|
|
152
159
|
|
|
153
160
|
@invalid_request_reason = :invalid_code_challenge
|
|
@@ -180,7 +187,7 @@ module Doorkeeper
|
|
|
180
187
|
scope: scope,
|
|
181
188
|
client_name: client.name,
|
|
182
189
|
status: I18n.t("doorkeeper.pre_authorization.status"),
|
|
183
|
-
}
|
|
190
|
+
}.reverse_merge(custom_access_token_attributes.symbolize_keys)
|
|
184
191
|
end
|
|
185
192
|
end
|
|
186
193
|
end
|
|
@@ -8,9 +8,13 @@ module Doorkeeper
|
|
|
8
8
|
class TokenIntrospection
|
|
9
9
|
attr_reader :token, :error, :invalid_request_reason
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# +token_type+ tells which credential of the token record the caller
|
|
12
|
+
# actually received (:access_token or :refresh_token), so the "active"
|
|
13
|
+
# state and the response describe the presented token per RFC 7662 §2.2.
|
|
14
|
+
def initialize(server, token, token_type: :access_token)
|
|
12
15
|
@server = server
|
|
13
16
|
@token = token
|
|
17
|
+
@token_type = token_type
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
def authorized?
|
|
@@ -106,12 +110,19 @@ module Doorkeeper
|
|
|
106
110
|
active: true,
|
|
107
111
|
scope: @token.scopes_string,
|
|
108
112
|
client_id: @token.try(:application).try(:uid),
|
|
109
|
-
token_type: @token.token_type,
|
|
110
113
|
iat: @token.created_at.to_i,
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
+
|
|
116
|
+
# `token_type` (RFC 6749 §7.1) and `exp` describe the access token:
|
|
117
|
+
# a refresh token is not a Bearer credential and has no expiry of its
|
|
118
|
+
# own, so both are omitted (they are OPTIONAL per RFC 7662 §2.2) when
|
|
119
|
+
# a refresh token is presented.
|
|
120
|
+
unless refresh_token_presented?
|
|
121
|
+
response[:token_type] = @token.token_type
|
|
122
|
+
# `exp` is OPTIONAL per RFC 7662 §2.2; omit it for non-expiring tokens
|
|
123
|
+
# so clients don't interpret `0` as "expired at 1970-01-01".
|
|
124
|
+
response[:exp] = @token.expires_at.to_i if @token.expires_at
|
|
125
|
+
end
|
|
115
126
|
|
|
116
127
|
customize_response(response)
|
|
117
128
|
end
|
|
@@ -176,9 +187,18 @@ module Doorkeeper
|
|
|
176
187
|
end
|
|
177
188
|
end
|
|
178
189
|
|
|
179
|
-
#
|
|
190
|
+
# The presented token can be valid only if it is not revoked; an access
|
|
191
|
+
# token must additionally be unexpired. A refresh token has no expiry of
|
|
192
|
+
# its own and stays usable at the token endpoint after its paired access
|
|
193
|
+
# token expires, so that expiry is not consulted here.
|
|
180
194
|
def valid_token?
|
|
181
|
-
@token
|
|
195
|
+
return false if @token.blank?
|
|
196
|
+
|
|
197
|
+
refresh_token_presented? ? !@token.revoked? : @token.accessible?
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def refresh_token_presented?
|
|
201
|
+
@token_type == :refresh_token
|
|
182
202
|
end
|
|
183
203
|
|
|
184
204
|
def valid_authorized_token?
|
|
@@ -116,9 +116,9 @@ module Doorkeeper::Orm::ActiveRecord::Mixins
|
|
|
116
116
|
|
|
117
117
|
return generator if generator.respond_to?(:generate)
|
|
118
118
|
|
|
119
|
-
raise Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
|
|
119
|
+
raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
|
|
120
120
|
rescue NameError
|
|
121
|
-
raise Errors::TokenGeneratorNotFound, "#{generator_name} not found"
|
|
121
|
+
raise Doorkeeper::Errors::TokenGeneratorNotFound, "#{generator_name} not found"
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def generate_uid
|
|
@@ -11,6 +11,7 @@ module Doorkeeper
|
|
|
11
11
|
authorizations: "doorkeeper/authorizations",
|
|
12
12
|
applications: "doorkeeper/applications",
|
|
13
13
|
authorized_applications: "doorkeeper/authorized_applications",
|
|
14
|
+
metadata: "doorkeeper/metadata",
|
|
14
15
|
tokens: "doorkeeper/tokens",
|
|
15
16
|
token_info: "doorkeeper/token_info",
|
|
16
17
|
}
|
|
@@ -41,10 +41,16 @@ module Doorkeeper
|
|
|
41
41
|
map_route(:authorized_applications, :authorized_applications_routes)
|
|
42
42
|
map_route(:token_info, :token_info_routes)
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
map_route(:metadata, :metadata_routes)
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
private
|
|
47
49
|
|
|
50
|
+
def metadata_routes(mapping)
|
|
51
|
+
routes.get ".well-known/oauth-authorization-server", controller: mapping[:controllers], action: :show
|
|
52
|
+
end
|
|
53
|
+
|
|
48
54
|
def authorization_routes(mapping)
|
|
49
55
|
routes.resource(
|
|
50
56
|
:authorization,
|
data/lib/doorkeeper/request.rb
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
module Doorkeeper
|
|
4
4
|
module Request
|
|
5
5
|
class << self
|
|
6
|
+
# Detect the OAuth client authentication method (RFC 6749 §2.3) that the
|
|
7
|
+
# given request uses. Returns the matching method's strategy (not the
|
|
8
|
+
# registry's Method wrapper), or FallbackMethod when none matches
|
|
9
|
+
# (which authenticates to no credentials).
|
|
10
|
+
#
|
|
11
|
+
# Raises Errors::MultipleClientAuthMethods when the request itself
|
|
12
|
+
# uses more than one client authentication method, since RFC 6749 §2.3
|
|
13
|
+
# forbids that (see +validate_client_authentication!+).
|
|
14
|
+
def client_authentication_method(request)
|
|
15
|
+
validate_client_authentication!(request)
|
|
16
|
+
|
|
17
|
+
authentication_method = client_authentication_methods.detect do |method|
|
|
18
|
+
method.matches_request?(request)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if authentication_method
|
|
22
|
+
authentication_method.strategy
|
|
23
|
+
else
|
|
24
|
+
Doorkeeper::ClientAuthentication::FallbackMethod
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
6
28
|
def authorization_strategy(response_type)
|
|
7
29
|
grant_flow = authorization_flows.detect do |flow|
|
|
8
30
|
flow.matches_response_type?(response_type)
|
|
@@ -40,6 +62,43 @@ module Doorkeeper
|
|
|
40
62
|
|
|
41
63
|
private
|
|
42
64
|
|
|
65
|
+
# RFC 6749 §2.3 forbids clients to "use more than one authentication
|
|
66
|
+
# method in each request", so the request payload is validated against
|
|
67
|
+
# every *registered* method — regardless of which ones are configured —
|
|
68
|
+
# before any method is selected: a client sending, say, both Basic
|
|
69
|
+
# credentials and body credentials is rejected even when only one of
|
|
70
|
+
# those methods is enabled on the server.
|
|
71
|
+
#
|
|
72
|
+
# Only real authentication mechanisms count towards the limit:
|
|
73
|
+
#
|
|
74
|
+
# * +:none+ is the absence of client authentication (a public client
|
|
75
|
+
# identifying itself with a bare +client_id+), not a mechanism of its
|
|
76
|
+
# own — RFC 7521 §4.2, for example, explicitly allows a +client_id+
|
|
77
|
+
# next to a client assertion.
|
|
78
|
+
# * Deprecated +client_credentials+ callable extractors are
|
|
79
|
+
# configuration adapters rather than registered methods, so they
|
|
80
|
+
# cannot count either; they keep the historical "first extractor that
|
|
81
|
+
# returns a uid wins" selection (see
|
|
82
|
+
# ClientAuthentication::LegacyCallable) for the deprecation window.
|
|
83
|
+
def validate_client_authentication!(request)
|
|
84
|
+
matched = 0
|
|
85
|
+
|
|
86
|
+
Doorkeeper::ClientAuthentication.registered_methods.each_value do |method|
|
|
87
|
+
next if method.name == :none
|
|
88
|
+
next unless method.matches_request?(request)
|
|
89
|
+
|
|
90
|
+
matched += 1
|
|
91
|
+
|
|
92
|
+
# RFC 6749 §2.3 only forbids using more than one method, so bail out
|
|
93
|
+
# on the second match instead of evaluating the remaining methods.
|
|
94
|
+
raise Errors::MultipleClientAuthMethods if matched > 1
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def client_authentication_methods
|
|
99
|
+
Doorkeeper.configuration.client_authentication_methods
|
|
100
|
+
end
|
|
101
|
+
|
|
43
102
|
def authorization_flows
|
|
44
103
|
Doorkeeper.configuration.authorization_response_flows
|
|
45
104
|
end
|
data/lib/doorkeeper/server.rb
CHANGED
|
@@ -8,6 +8,10 @@ module Doorkeeper
|
|
|
8
8
|
@context = context
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
def client_authentication_method_for_request
|
|
12
|
+
Request.client_authentication_method(context.request)
|
|
13
|
+
end
|
|
14
|
+
|
|
11
15
|
def authorization_request(strategy)
|
|
12
16
|
klass = Request.authorization_strategy(strategy)
|
|
13
17
|
klass.new(self)
|
|
@@ -37,8 +41,7 @@ module Doorkeeper
|
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def credentials
|
|
40
|
-
|
|
41
|
-
@credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods)
|
|
44
|
+
@credentials ||= client_authentication_method_for_request.authenticate(context.request)
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
47
|
end
|
data/lib/doorkeeper/version.rb
CHANGED
data/lib/doorkeeper.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "doorkeeper/engine"
|
|
|
7
7
|
#
|
|
8
8
|
module Doorkeeper
|
|
9
9
|
autoload :Errors, "doorkeeper/errors"
|
|
10
|
+
autoload :ClientAuthentication, "doorkeeper/client_authentication"
|
|
10
11
|
autoload :GrantFlow, "doorkeeper/grant_flow"
|
|
11
12
|
autoload :OAuth, "doorkeeper/oauth"
|
|
12
13
|
autoload :Rake, "doorkeeper/rake"
|
|
@@ -52,6 +53,7 @@ module Doorkeeper
|
|
|
52
53
|
autoload :InvalidTokenResponse, "doorkeeper/oauth/invalid_token_response"
|
|
53
54
|
autoload :InvalidRequestResponse, "doorkeeper/oauth/invalid_request_response"
|
|
54
55
|
autoload :ForbiddenTokenResponse, "doorkeeper/oauth/forbidden_token_response"
|
|
56
|
+
autoload :MetadataResponse, "doorkeeper/oauth/metadata_response"
|
|
55
57
|
autoload :NonStandard, "doorkeeper/oauth/nonstandard"
|
|
56
58
|
autoload :PasswordAccessTokenRequest, "doorkeeper/oauth/password_access_token_request"
|
|
57
59
|
autoload :PreAuthorization, "doorkeeper/oauth/pre_authorization"
|
|
@@ -62,6 +64,12 @@ module Doorkeeper
|
|
|
62
64
|
autoload :TokenRequest, "doorkeeper/oauth/token_request"
|
|
63
65
|
autoload :TokenResponse, "doorkeeper/oauth/token_response"
|
|
64
66
|
|
|
67
|
+
module ClientAuthentication
|
|
68
|
+
autoload :None, "doorkeeper/oauth/client_authentication/none"
|
|
69
|
+
autoload :ClientSecretBasic, "doorkeeper/oauth/client_authentication/client_secret_basic"
|
|
70
|
+
autoload :ClientSecretPost, "doorkeeper/oauth/client_authentication/client_secret_post"
|
|
71
|
+
end
|
|
72
|
+
|
|
65
73
|
module Authorization
|
|
66
74
|
autoload :Code, "doorkeeper/oauth/authorization/code"
|
|
67
75
|
autoload :Context, "doorkeeper/oauth/authorization/context"
|
|
@@ -69,10 +77,6 @@ module Doorkeeper
|
|
|
69
77
|
autoload :URIBuilder, "doorkeeper/oauth/authorization/uri_builder"
|
|
70
78
|
end
|
|
71
79
|
|
|
72
|
-
class Client
|
|
73
|
-
autoload :Credentials, "doorkeeper/oauth/client/credentials"
|
|
74
|
-
end
|
|
75
|
-
|
|
76
80
|
module ClientCredentials
|
|
77
81
|
autoload :Validator, "doorkeeper/oauth/client_credentials/validator"
|
|
78
82
|
autoload :Creator, "doorkeeper/oauth/client_credentials/creator"
|
|
@@ -150,6 +150,15 @@ Doorkeeper.configure do
|
|
|
150
150
|
# token found for the application, resources owner and/or set of scopes.
|
|
151
151
|
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383
|
|
152
152
|
#
|
|
153
|
+
# Matching considers only the application, resource owner, scopes, custom access token
|
|
154
|
+
# attributes (see +custom_access_token_attributes+) and whether a refresh token is
|
|
155
|
+
# expected — not the authorization request that produced the token. Separate authorization
|
|
156
|
+
# grants for the same combination share a single access token, so concurrent sessions of
|
|
157
|
+
# the same client become interdependent (e.g. refreshing the token in one session revokes
|
|
158
|
+
# it for the others). If you need independent tokens per session or device, keep this
|
|
159
|
+
# option disabled or differentiate the sessions with +custom_access_token_attributes+.
|
|
160
|
+
# See https://github.com/doorkeeper-gem/doorkeeper/issues/1693
|
|
161
|
+
#
|
|
153
162
|
# You can not enable this option together with +hash_token_secrets+.
|
|
154
163
|
#
|
|
155
164
|
# reuse_access_token
|
|
@@ -188,8 +197,8 @@ Doorkeeper.configure do
|
|
|
188
197
|
#
|
|
189
198
|
# revoke_previous_authorization_code_token
|
|
190
199
|
|
|
191
|
-
# Require
|
|
192
|
-
# to obtain an access_token (disabled by default)
|
|
200
|
+
# Require all clients (including confidential ones) to use PKCE when using an
|
|
201
|
+
# authorization code to obtain an access_token (disabled by default)
|
|
193
202
|
#
|
|
194
203
|
# force_pkce
|
|
195
204
|
|
|
@@ -277,13 +286,18 @@ Doorkeeper.configure do
|
|
|
277
286
|
#
|
|
278
287
|
# enforce_configured_scopes
|
|
279
288
|
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
#
|
|
289
|
+
# Configure the OAuth client authentication methods (RFC 6749 §2.3) Doorkeeper
|
|
290
|
+
# will accept and the order in which they are tried. By default it accepts
|
|
291
|
+
# HTTP Basic auth (`client_secret_basic`), credentials in the request body
|
|
292
|
+
# (`client_secret_post`), and public clients with no secret (`none`).
|
|
283
293
|
# Check out https://github.com/doorkeeper-gem/doorkeeper/wiki/Changing-how-clients-are-authenticated
|
|
284
294
|
# for more information on customization
|
|
285
295
|
#
|
|
286
|
-
#
|
|
296
|
+
# client_authentication %i[client_secret_basic client_secret_post none]
|
|
297
|
+
#
|
|
298
|
+
# The legacy `client_credentials` option is deprecated; `:from_basic` and
|
|
299
|
+
# `:from_params` are automatically mapped to `:client_secret_basic` and
|
|
300
|
+
# `:client_secret_post`.
|
|
287
301
|
|
|
288
302
|
# Change the way access token is authenticated from the request object.
|
|
289
303
|
# By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
|
|
@@ -377,6 +391,9 @@ Doorkeeper.configure do
|
|
|
377
391
|
# If not specified, Doorkeeper enables authorization_code and
|
|
378
392
|
# client_credentials.
|
|
379
393
|
#
|
|
394
|
+
# The Refresh Token Grant Flow ("refresh_token") doesn't need to be listed
|
|
395
|
+
# here: it is enabled automatically when +use_refresh_token+ is configured.
|
|
396
|
+
#
|
|
380
397
|
# implicit and password grant flows have risks that you should understand
|
|
381
398
|
# before enabling:
|
|
382
399
|
# https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.2
|
|
@@ -541,4 +558,47 @@ Doorkeeper.configure do
|
|
|
541
558
|
# WWW-Authenticate Realm (default: "Doorkeeper").
|
|
542
559
|
#
|
|
543
560
|
# realm "Doorkeeper"
|
|
561
|
+
|
|
562
|
+
# OAuth 2.0 Authorization Server Metadata (RFC 8414).
|
|
563
|
+
#
|
|
564
|
+
# Doorkeeper exposes an authorization server metadata document at
|
|
565
|
+
# `/.well-known/oauth-authorization-server`, built from the configuration
|
|
566
|
+
# above. The two options below let you customize that document.
|
|
567
|
+
#
|
|
568
|
+
# `issuer` is the authorization server's issuer identifier. When left as nil
|
|
569
|
+
# (the default) the request base URL is used for the metadata `issuer` field
|
|
570
|
+
# only. Note the asymmetry: RFC 9207 below is gated on an explicitly
|
|
571
|
+
# configured issuer, so leaving it nil still advertises a metadata `issuer`
|
|
572
|
+
# (the base URL) while `authorization_response_iss_parameter_supported` stays
|
|
573
|
+
# false and no `iss` parameter is emitted.
|
|
574
|
+
#
|
|
575
|
+
# Configuring an issuer also enables RFC 9207 (Authorization Server Issuer
|
|
576
|
+
# Identification): the `iss` parameter is added to the authorization
|
|
577
|
+
# responses redirected back to the client - both successful responses and
|
|
578
|
+
# error responses such as access_denied - and advertised via the
|
|
579
|
+
# `authorization_response_iss_parameter_supported` metadata field. Clients
|
|
580
|
+
# that parse the authorization redirect will start seeing this parameter.
|
|
581
|
+
# Per RFC 8414 and RFC 9207 the value should be an https URL with no query or
|
|
582
|
+
# fragment; a non-compliant value logs a warning at boot but is still used.
|
|
583
|
+
# Prefer a host-only issuer: Doorkeeper serves its metadata only at the root
|
|
584
|
+
# /.well-known/oauth-authorization-server, so a path-bearing issuer (e.g.
|
|
585
|
+
# https://auth.example.com/tenant) is not discoverable by RFC 8414 clients and
|
|
586
|
+
# also logs a warning.
|
|
587
|
+
#
|
|
588
|
+
# issuer "https://auth.example.com"
|
|
589
|
+
#
|
|
590
|
+
# `custom_metadata` is a Hash that is merged into the metadata response. Use
|
|
591
|
+
# it to advertise additional or non-default metadata fields, for example a
|
|
592
|
+
# `userinfo_endpoint` (which Doorkeeper itself leaves null) when pairing with
|
|
593
|
+
# an OpenID Connect extension.
|
|
594
|
+
#
|
|
595
|
+
# The merge happens last, so it can also override computed fields - including
|
|
596
|
+
# `authorization_response_iss_parameter_supported`, which Doorkeeper derives
|
|
597
|
+
# from whether `issuer` is set. Overriding a computed field is allowed but is
|
|
598
|
+
# your responsibility to keep consistent with the server's actual behaviour
|
|
599
|
+
# (e.g. don't advertise iss support as true if no `issuer` is configured).
|
|
600
|
+
#
|
|
601
|
+
# custom_metadata(
|
|
602
|
+
# userinfo_endpoint: "https://auth.example.com/oauth/userinfo",
|
|
603
|
+
# )
|
|
544
604
|
end
|
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:
|
|
4
|
+
version: 6.0.0.beta1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Felipe Elias Philipp
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- app/controllers/doorkeeper/applications_controller.rb
|
|
184
184
|
- app/controllers/doorkeeper/authorizations_controller.rb
|
|
185
185
|
- app/controllers/doorkeeper/authorized_applications_controller.rb
|
|
186
|
+
- app/controllers/doorkeeper/metadata_controller.rb
|
|
186
187
|
- app/controllers/doorkeeper/token_info_controller.rb
|
|
187
188
|
- app/controllers/doorkeeper/tokens_controller.rb
|
|
188
189
|
- app/helpers/doorkeeper/dashboard_helper.rb
|
|
@@ -202,6 +203,12 @@ files:
|
|
|
202
203
|
- app/views/layouts/doorkeeper/application.html.erb
|
|
203
204
|
- config/locales/en.yml
|
|
204
205
|
- lib/doorkeeper.rb
|
|
206
|
+
- lib/doorkeeper/client_authentication.rb
|
|
207
|
+
- lib/doorkeeper/client_authentication/credentials.rb
|
|
208
|
+
- lib/doorkeeper/client_authentication/fallback_method.rb
|
|
209
|
+
- lib/doorkeeper/client_authentication/legacy_callable.rb
|
|
210
|
+
- lib/doorkeeper/client_authentication/method.rb
|
|
211
|
+
- lib/doorkeeper/client_authentication/registry.rb
|
|
205
212
|
- lib/doorkeeper/config.rb
|
|
206
213
|
- lib/doorkeeper/config/abstract_builder.rb
|
|
207
214
|
- lib/doorkeeper/config/option.rb
|
|
@@ -239,7 +246,9 @@ files:
|
|
|
239
246
|
- lib/doorkeeper/oauth/base_request.rb
|
|
240
247
|
- lib/doorkeeper/oauth/base_response.rb
|
|
241
248
|
- lib/doorkeeper/oauth/client.rb
|
|
242
|
-
- lib/doorkeeper/oauth/
|
|
249
|
+
- lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb
|
|
250
|
+
- lib/doorkeeper/oauth/client_authentication/client_secret_post.rb
|
|
251
|
+
- lib/doorkeeper/oauth/client_authentication/none.rb
|
|
243
252
|
- lib/doorkeeper/oauth/client_credentials/creator.rb
|
|
244
253
|
- lib/doorkeeper/oauth/client_credentials/issuer.rb
|
|
245
254
|
- lib/doorkeeper/oauth/client_credentials/validator.rb
|
|
@@ -255,6 +264,7 @@ files:
|
|
|
255
264
|
- lib/doorkeeper/oauth/hooks/context.rb
|
|
256
265
|
- lib/doorkeeper/oauth/invalid_request_response.rb
|
|
257
266
|
- lib/doorkeeper/oauth/invalid_token_response.rb
|
|
267
|
+
- lib/doorkeeper/oauth/metadata_response.rb
|
|
258
268
|
- lib/doorkeeper/oauth/nonstandard.rb
|
|
259
269
|
- lib/doorkeeper/oauth/password_access_token_request.rb
|
|
260
270
|
- lib/doorkeeper/oauth/pre_authorization.rb
|