doorkeeper 5.9.5 → 6.0.0.beta2
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 +70 -3
- data/README.md +204 -0
- data/app/controllers/doorkeeper/applications_controller.rb +38 -9
- data/app/controllers/doorkeeper/authorizations_controller.rb +17 -3
- data/app/controllers/doorkeeper/metadata_controller.rb +20 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +15 -2
- data/app/views/doorkeeper/authorizations/new.html.erb +24 -0
- data/config/locales/en.yml +2 -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 +52 -0
- data/lib/doorkeeper/client_authentication/method.rb +40 -0
- data/lib/doorkeeper/client_authentication/registry.rb +47 -0
- data/lib/doorkeeper/client_authentication/verified_credentials.rb +19 -0
- data/lib/doorkeeper/client_authentication.rb +102 -0
- data/lib/doorkeeper/config/option.rb +1 -1
- data/lib/doorkeeper/config/validations.rb +162 -1
- data/lib/doorkeeper/config.rb +161 -9
- data/lib/doorkeeper/document_cache.rb +81 -0
- data/lib/doorkeeper/errors.rb +49 -0
- data/lib/doorkeeper/http_fetcher.rb +232 -0
- data/lib/doorkeeper/models/access_grant_mixin.rb +16 -0
- data/lib/doorkeeper/models/access_token_mixin.rb +81 -5
- data/lib/doorkeeper/models/application_mixin.rb +14 -6
- data/lib/doorkeeper/models/concerns/secret_storable.rb +10 -1
- data/lib/doorkeeper/oauth/authorization/code.rb +10 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +13 -2
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +11 -0
- data/lib/doorkeeper/oauth/authorization_code_request.rb +127 -3
- data/lib/doorkeeper/oauth/base_request.rb +1 -2
- data/lib/doorkeeper/oauth/client.rb +24 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +57 -0
- data/lib/doorkeeper/oauth/client_authentication/client_secret_post.rb +33 -0
- data/lib/doorkeeper/oauth/client_authentication/none.rb +71 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/key_resolver.rb +104 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt/replay_guard.rb +78 -0
- data/lib/doorkeeper/oauth/client_authentication/private_key_jwt.rb +247 -0
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +25 -9
- data/lib/doorkeeper/oauth/client_credentials_request.rb +36 -5
- data/lib/doorkeeper/oauth/code_response.rb +16 -2
- data/lib/doorkeeper/oauth/error_response.rb +11 -2
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +35 -23
- data/lib/doorkeeper/oauth/metadata_response.rb +165 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +27 -1
- data/lib/doorkeeper/oauth/pre_authorization.rb +63 -8
- data/lib/doorkeeper/oauth/refresh_token_request.rb +51 -1
- data/lib/doorkeeper/oauth/resource_indicator_validator.rb +70 -0
- data/lib/doorkeeper/oauth/scopes.rb +25 -0
- data/lib/doorkeeper/oauth/token_introspection.rb +33 -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 +12 -4
- data/lib/generators/doorkeeper/grant_reuse_revocation_generator.rb +43 -0
- data/lib/generators/doorkeeper/resource_indicators_generator.rb +33 -0
- data/lib/generators/doorkeeper/templates/add_access_token_to_access_grants.rb.erb +10 -0
- data/lib/generators/doorkeeper/templates/enable_resource_indicators_migration.rb.erb +8 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +131 -6
- data/lib/generators/doorkeeper/templates/migration.rb.erb +13 -0
- metadata +52 -4
- data/lib/doorkeeper/oauth/client/credentials.rb +0 -71
- data/lib/doorkeeper/oauth/stateless_token.rb +0 -139
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module ClientAuthentication
|
|
5
|
+
# Adapter that lets a legacy callable +client_credentials+ extractor
|
|
6
|
+
# (e.g. +->(request) { [client_id, client_secret] }+) keep working through
|
|
7
|
+
# the client authentication registry during the deprecation window.
|
|
8
|
+
#
|
|
9
|
+
# The callable is expected to return a +[uid, secret]+ pair (or +nil+ when
|
|
10
|
+
# it does not apply), matching the historical +Credentials.from_request+
|
|
11
|
+
# contract. It is considered to match a request whenever it yields present
|
|
12
|
+
# credentials, mirroring the previous "first extractor that returns a uid
|
|
13
|
+
# wins" behaviour.
|
|
14
|
+
class LegacyCallable
|
|
15
|
+
def initialize(callable)
|
|
16
|
+
@callable = callable
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Legacy extractors pull a uid/secret pair out of the request, so they
|
|
20
|
+
# are treated as shared-secret methods.
|
|
21
|
+
def uses_shared_secret?
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def matches_request?(request)
|
|
26
|
+
credentials_for(request).present?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def authenticate(request)
|
|
30
|
+
credentials_for(request)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Invoke the wrapped extractor at most once per request. +matches_request?+
|
|
36
|
+
# and +authenticate+ both need the extracted credentials, and the legacy
|
|
37
|
+
# +Credentials.from_request+ contract called each extractor exactly once,
|
|
38
|
+
# so cache the result on the request itself to preserve that. The adapter
|
|
39
|
+
# is stored in the config/registry and therefore shared across requests
|
|
40
|
+
# and threads, so the cache must live on the per-request +env+ (keyed per
|
|
41
|
+
# adapter instance) rather than on the adapter, which would otherwise race.
|
|
42
|
+
def credentials_for(request)
|
|
43
|
+
request.env[cache_key] ||=
|
|
44
|
+
Doorkeeper::ClientAuthentication::Credentials.new(*@callable.call(request))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cache_key
|
|
48
|
+
@cache_key ||= "doorkeeper.client_authentication.legacy_callable.#{@callable.object_id}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module ClientAuthentication
|
|
5
|
+
# Wraps a registered client authentication method, pairing its
|
|
6
|
+
# registration +name+ with the +strategy+ object that knows how to match
|
|
7
|
+
# and authenticate a request.
|
|
8
|
+
#
|
|
9
|
+
# NOTE: the wrapped object is exposed as +strategy+ rather than +method+ on
|
|
10
|
+
# purpose — an +attr_reader :method+ would shadow Ruby's core
|
|
11
|
+
# +Object#method+ reflection API and break +wrapper.method(:authenticate)+.
|
|
12
|
+
class Method
|
|
13
|
+
attr_reader :name, :strategy
|
|
14
|
+
|
|
15
|
+
delegate :matches_request?, :authenticate, to: :strategy
|
|
16
|
+
|
|
17
|
+
def initialize(name, strategy)
|
|
18
|
+
@name = name
|
|
19
|
+
@strategy = strategy
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Whether this method authenticates clients with a shared symmetric
|
|
23
|
+
# secret. Callers that must refuse such methods — because no secret
|
|
24
|
+
# can have been established with the client — can ask the registry
|
|
25
|
+
# instead of keeping a hard-coded list of method names.
|
|
26
|
+
#
|
|
27
|
+
# Strategies may declare this themselves by defining
|
|
28
|
+
# +uses_shared_secret?+; for strategies that don't, the registration
|
|
29
|
+
# name is checked for "client_secret" as a conservative fallback, so an
|
|
30
|
+
# undeclared method errs on the side of being treated as secret-based.
|
|
31
|
+
def uses_shared_secret?
|
|
32
|
+
if strategy.respond_to?(:uses_shared_secret?)
|
|
33
|
+
strategy.uses_shared_secret?
|
|
34
|
+
else
|
|
35
|
+
name.to_s.include?("client_secret")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module ClientAuthentication
|
|
5
|
+
# Holds the registered client authentication methods and provides the DSL
|
|
6
|
+
# to register and look them up by name.
|
|
7
|
+
module Registry
|
|
8
|
+
mattr_accessor :registered_methods
|
|
9
|
+
self.registered_methods = {}
|
|
10
|
+
|
|
11
|
+
# Allows to register a custom OAuth client authentication method so that
|
|
12
|
+
# Doorkeeper could recognize and process it.
|
|
13
|
+
#
|
|
14
|
+
def register(name, method)
|
|
15
|
+
unless name.respond_to?(:to_sym)
|
|
16
|
+
raise ArgumentError,
|
|
17
|
+
"client authentication method name must be a Symbol or String, got #{name.inspect}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
unless method.respond_to?(:matches_request?) && method.respond_to?(:authenticate)
|
|
21
|
+
raise ArgumentError,
|
|
22
|
+
"client authentication method '#{name}' must respond to " \
|
|
23
|
+
"#matches_request? and #authenticate, got #{method.inspect}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
name_key = name.to_sym
|
|
27
|
+
|
|
28
|
+
if registered_methods.key?(name_key)
|
|
29
|
+
::Kernel.warn <<~WARNING
|
|
30
|
+
[DOORKEEPER] '#{name_key}' client authentication strategy is already registered and will be overridden
|
|
31
|
+
in #{caller(1..1).first}
|
|
32
|
+
WARNING
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
registered_methods[name_key] = Doorkeeper::ClientAuthentication::Method.new(name_key, method)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# [NOTE]: switch to #fetch once the deprecated client_credentials
|
|
39
|
+
# fallbacks are removed.
|
|
40
|
+
def get(name)
|
|
41
|
+
return unless name.respond_to?(:to_sym)
|
|
42
|
+
|
|
43
|
+
registered_methods[name.to_sym]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module ClientAuthentication
|
|
5
|
+
# Credentials for a client that was already fully authenticated by its
|
|
6
|
+
# authentication method (e.g. a verified private_key_jwt assertion), so
|
|
7
|
+
# the client lookup must not run a secret comparison — there is no
|
|
8
|
+
# secret, and the proof of identity has already been checked.
|
|
9
|
+
class VerifiedCredentials < Credentials
|
|
10
|
+
def initialize(uid)
|
|
11
|
+
super(uid, nil)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pre_authenticated?
|
|
15
|
+
true
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "doorkeeper/client_authentication/credentials"
|
|
4
|
+
require "doorkeeper/client_authentication/verified_credentials"
|
|
5
|
+
require "doorkeeper/client_authentication/fallback_method"
|
|
6
|
+
require "doorkeeper/client_authentication/legacy_callable"
|
|
7
|
+
require "doorkeeper/client_authentication/method"
|
|
8
|
+
require "doorkeeper/client_authentication/registry"
|
|
9
|
+
|
|
10
|
+
module Doorkeeper
|
|
11
|
+
# Registry of the OAuth client authentication methods (RFC 6749 §2.3)
|
|
12
|
+
# Doorkeeper knows how to process. Each registered method is able to tell
|
|
13
|
+
# whether it +matches_request?+ and how to +authenticate+ it into a
|
|
14
|
+
# Credentials object.
|
|
15
|
+
module ClientAuthentication
|
|
16
|
+
extend Registry
|
|
17
|
+
|
|
18
|
+
# Default ordered client authentication methods (RFC 6749 §2.3) used when
|
|
19
|
+
# +client_authentication+ is not configured.
|
|
20
|
+
DEFAULT_METHODS = %i[client_secret_basic client_secret_post none].freeze
|
|
21
|
+
|
|
22
|
+
# Maps the current method names back onto the deprecated +client_credentials+
|
|
23
|
+
# names so the +client_credentials_methods+ alias can keep returning the
|
|
24
|
+
# legacy symbols that external callers (e.g. doorkeeper-openid_connect) map
|
|
25
|
+
# from. Names without a legacy equivalent (e.g. +:none+, custom methods) are
|
|
26
|
+
# returned unchanged — consumers that don't recognise them drop them, just
|
|
27
|
+
# as they did before.
|
|
28
|
+
LEGACY_NAME_FOR = {
|
|
29
|
+
client_secret_basic: :from_basic,
|
|
30
|
+
client_secret_post: :from_params,
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
# Converts resolved Method objects into the legacy client_credentials
|
|
34
|
+
# symbol names for the deprecated +client_credentials_methods+ alias.
|
|
35
|
+
def self.to_legacy_client_credentials_names(methods)
|
|
36
|
+
methods.map { |method| LEGACY_NAME_FOR.fetch(method.name, method.name) }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
register(
|
|
40
|
+
:none,
|
|
41
|
+
Doorkeeper::OAuth::ClientAuthentication::None,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
register(
|
|
45
|
+
:client_secret_post,
|
|
46
|
+
Doorkeeper::OAuth::ClientAuthentication::ClientSecretPost,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
register(
|
|
50
|
+
:client_secret_basic,
|
|
51
|
+
Doorkeeper::OAuth::ClientAuthentication::ClientSecretBasic,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
# Registered but not part of DEFAULT_METHODS: servers opt in through the
|
|
55
|
+
# +client_authentication+ config option. Requires the "jwt" gem at the
|
|
56
|
+
# moment an assertion is authenticated.
|
|
57
|
+
register(
|
|
58
|
+
:private_key_jwt,
|
|
59
|
+
Doorkeeper::OAuth::ClientAuthentication::PrivateKeyJwt,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Converts a deprecated +client_credentials+ configuration into the client
|
|
63
|
+
# authentication method names / adapters understood by the registry.
|
|
64
|
+
# Unknown values are warned about and dropped; callables are wrapped in a
|
|
65
|
+
# LegacyCallable adapter. +:none+ (public client support) is appended only
|
|
66
|
+
# for +:from_params+ — the sole legacy method that accepted a bare
|
|
67
|
+
# +client_id+ without a secret — so a Basic-only configuration is not
|
|
68
|
+
# silently broadened.
|
|
69
|
+
def self.from_legacy_client_credentials(methods)
|
|
70
|
+
converted = methods.filter_map { |method| legacy_client_credential(method) }
|
|
71
|
+
converted.push(:none) if methods.include?(:from_params)
|
|
72
|
+
converted
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.legacy_client_credential(method)
|
|
76
|
+
case method
|
|
77
|
+
when :from_basic
|
|
78
|
+
:client_secret_basic
|
|
79
|
+
when :from_params
|
|
80
|
+
:client_secret_post
|
|
81
|
+
else
|
|
82
|
+
legacy_extractor_or_nil(method)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
private_class_method :legacy_client_credential
|
|
86
|
+
|
|
87
|
+
def self.legacy_extractor_or_nil(method)
|
|
88
|
+
unless method.respond_to?(:call)
|
|
89
|
+
Kernel.warn("[DOORKEEPER] Unknown client_credentials method detected: #{method}")
|
|
90
|
+
return nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Kernel.warn(
|
|
94
|
+
"[DOORKEEPER] client_credentials callable extractors are deprecated; wrapping it in a " \
|
|
95
|
+
"legacy client authentication adapter. Register it via " \
|
|
96
|
+
"Doorkeeper::ClientAuthentication.register instead.",
|
|
97
|
+
)
|
|
98
|
+
Method.new(:legacy_callable, LegacyCallable.new(method))
|
|
99
|
+
end
|
|
100
|
+
private_class_method :legacy_extractor_or_nil
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -74,7 +74,7 @@ module Doorkeeper
|
|
|
74
74
|
def self.extended(base)
|
|
75
75
|
return if base.respond_to?(:builder_class)
|
|
76
76
|
|
|
77
|
-
raise Doorkeeper::MissingConfigurationBuilderClass, "Define `self.builder_class` method " \
|
|
77
|
+
raise Doorkeeper::Errors::MissingConfigurationBuilderClass, "Define `self.builder_class` method " \
|
|
78
78
|
"for #{base} that returns your custom Builder class to use options DSL!"
|
|
79
79
|
end
|
|
80
80
|
end
|
|
@@ -8,14 +8,68 @@ module Doorkeeper
|
|
|
8
8
|
# Validates configuration options to be set properly.
|
|
9
9
|
#
|
|
10
10
|
def validate!
|
|
11
|
+
validate_client_authentication_conflict
|
|
12
|
+
validate_client_authentication_registered
|
|
11
13
|
validate_reuse_access_token_value
|
|
12
14
|
validate_token_reuse_limit
|
|
13
15
|
validate_secret_strategies
|
|
14
16
|
validate_pkce_code_challenge_methods
|
|
17
|
+
validate_custom_metadata
|
|
18
|
+
validate_refresh_token_flow
|
|
19
|
+
validate_issuer_format
|
|
20
|
+
validate_issuer_metadata_discoverability
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
private
|
|
18
24
|
|
|
25
|
+
# Warn once, at configuration time, when both the deprecated
|
|
26
|
+
# +client_credentials+ and the new +client_authentication+ options are
|
|
27
|
+
# set. +client_authentication+ takes precedence; the warning lives here
|
|
28
|
+
# (rather than in the memoised resolver) so it is not swallowed and
|
|
29
|
+
# surfaces during boot instead of on the first request.
|
|
30
|
+
def validate_client_authentication_conflict
|
|
31
|
+
return unless instance_variable_defined?(:@client_credentials_methods) &&
|
|
32
|
+
instance_variable_defined?(:@client_authentication)
|
|
33
|
+
|
|
34
|
+
::Rails.logger.warn(
|
|
35
|
+
"[DOORKEEPER] Both client_credentials and client_authentication are set, " \
|
|
36
|
+
"using client_authentication",
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Warn about configured client authentication methods that are not
|
|
41
|
+
# registered (e.g. a typo, or an extension that failed to load). Such
|
|
42
|
+
# names are silently ignored when resolving the methods, which could
|
|
43
|
+
# otherwise leave the application with no usable authentication methods.
|
|
44
|
+
def validate_client_authentication_registered
|
|
45
|
+
# The deprecated client_credentials path already validates its own input.
|
|
46
|
+
return if instance_variable_defined?(:@client_credentials_methods) &&
|
|
47
|
+
!instance_variable_defined?(:@client_authentication)
|
|
48
|
+
|
|
49
|
+
configured = client_authentication
|
|
50
|
+
unknown = configured.reject { |name| Doorkeeper::ClientAuthentication.get(name) }
|
|
51
|
+
|
|
52
|
+
unless unknown.empty?
|
|
53
|
+
::Rails.logger.warn(
|
|
54
|
+
"[DOORKEEPER] Unknown client authentication method(s) configured and will be ignored: " \
|
|
55
|
+
"#{unknown.map(&:inspect).join(", ")}. " \
|
|
56
|
+
"Ensure each method is registered (e.g. by the extension that provides it).",
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# A configuration that resolves to zero usable methods fails every
|
|
61
|
+
# client authentication attempt (all requests fall back to no
|
|
62
|
+
# credentials), so make that loud rather than silent.
|
|
63
|
+
return unless (configured - unknown).empty?
|
|
64
|
+
|
|
65
|
+
::Rails.logger.error(
|
|
66
|
+
"[DOORKEEPER] No usable client authentication methods are configured " \
|
|
67
|
+
"(client_authentication resolved to an empty set). All client authentication " \
|
|
68
|
+
"will fail. Configure at least one registered method, e.g. " \
|
|
69
|
+
"client_authentication #{Doorkeeper::ClientAuthentication::DEFAULT_METHODS.inspect}.",
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
19
73
|
# Determine whether +reuse_access_token+ and a non-restorable
|
|
20
74
|
# +token_secret_strategy+ have both been activated.
|
|
21
75
|
#
|
|
@@ -51,7 +105,15 @@ module Doorkeeper
|
|
|
51
105
|
end
|
|
52
106
|
|
|
53
107
|
def validate_pkce_code_challenge_methods
|
|
54
|
-
|
|
108
|
+
methods = pkce_code_challenge_methods.map(&:to_s)
|
|
109
|
+
if methods.all? { |method| method.match?(/\A(?:plain|S256)\z/) }
|
|
110
|
+
# Persist the normalized (string) values only when the option was
|
|
111
|
+
# explicitly configured — the default is already normalized, and an
|
|
112
|
+
# unconfigured option should stay undefined rather than have
|
|
113
|
+
# validation flip its instance_variable_defined? signal.
|
|
114
|
+
@pkce_code_challenge_methods = methods if instance_variable_defined?(:@pkce_code_challenge_methods)
|
|
115
|
+
return
|
|
116
|
+
end
|
|
55
117
|
|
|
56
118
|
::Rails.logger.warn(
|
|
57
119
|
"[DOORKEEPER] You have configured an invalid value for pkce_code_challenge_methods option. " \
|
|
@@ -60,6 +122,105 @@ module Doorkeeper
|
|
|
60
122
|
|
|
61
123
|
@pkce_code_challenge_methods = ["plain", "S256"]
|
|
62
124
|
end
|
|
125
|
+
|
|
126
|
+
def validate_custom_metadata
|
|
127
|
+
return if custom_metadata.is_a? Hash
|
|
128
|
+
|
|
129
|
+
::Rails.logger.warn(
|
|
130
|
+
"[DOORKEEPER] You have configured an invalid value for custom_metadata option. " \
|
|
131
|
+
"It must be a Hash, and will be overridden with an empty hash.",
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@custom_metadata = {}
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Warn when the refresh_token grant flow is enabled but refresh tokens
|
|
138
|
+
# are never issued: refresh token requests would always fail because
|
|
139
|
+
# there are no tokens to refresh. The flow is enabled automatically
|
|
140
|
+
# when +use_refresh_token+ is configured, so it doesn't need to be
|
|
141
|
+
# listed in +grant_flows+ explicitly.
|
|
142
|
+
#
|
|
143
|
+
# +calculate_grant_flows+ is used (rather than the raw +grant_flows+) so
|
|
144
|
+
# that the flow is also detected when enabled through a registered
|
|
145
|
+
# grant-flow alias. When +use_refresh_token+ is not configured the
|
|
146
|
+
# refresh_token flow is not appended implicitly, so its presence there
|
|
147
|
+
# means it was requested explicitly (directly or via an alias).
|
|
148
|
+
def validate_refresh_token_flow
|
|
149
|
+
return if refresh_token_enabled?
|
|
150
|
+
return unless calculate_grant_flows.map(&:to_s).include?("refresh_token")
|
|
151
|
+
|
|
152
|
+
::Rails.logger.warn(
|
|
153
|
+
"[DOORKEEPER] You have enabled the refresh_token grant flow without " \
|
|
154
|
+
"configuring use_refresh_token, so refresh tokens will not be issued. " \
|
|
155
|
+
"Configure use_refresh_token to issue refresh tokens (the refresh_token " \
|
|
156
|
+
"grant flow is then enabled automatically).",
|
|
157
|
+
)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Warn when a configured issuer is not RFC-compliant. RFC 8414 (the
|
|
161
|
+
# metadata issuer) and RFC 9207 (the authorization response iss parameter)
|
|
162
|
+
# both require an https URL with a host and no query or fragment component.
|
|
163
|
+
# The value is still used as-is - this is a warning, not a hard failure, so
|
|
164
|
+
# local setups using e.g. http://localhost keep working - but a
|
|
165
|
+
# non-compliant issuer produces responses that strict clients may reject.
|
|
166
|
+
def validate_issuer_format
|
|
167
|
+
return if issuer.blank?
|
|
168
|
+
|
|
169
|
+
uri =
|
|
170
|
+
begin
|
|
171
|
+
URI.parse(issuer.to_s)
|
|
172
|
+
rescue URI::InvalidURIError
|
|
173
|
+
nil
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
return if uri.is_a?(URI::HTTPS) && uri.host.present? &&
|
|
177
|
+
uri.query.nil? && uri.fragment.nil?
|
|
178
|
+
|
|
179
|
+
::Rails.logger.warn(
|
|
180
|
+
"[DOORKEEPER] issuer #{redacted_issuer.inspect} is not RFC-compliant: " \
|
|
181
|
+
"RFC 8414 and RFC 9207 require an https URL with a host and no query " \
|
|
182
|
+
"or fragment component. It is still advertised in the metadata and " \
|
|
183
|
+
"emitted as the iss parameter as-is, but strict clients may reject it.",
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Warn when a path-bearing issuer cannot be discovered. RFC 8414 clients
|
|
188
|
+
# build the metadata URL by inserting the well-known path into the issuer,
|
|
189
|
+
# so an issuer of https://host/tenant is looked up at
|
|
190
|
+
# https://host/.well-known/oauth-authorization-server/tenant. Doorkeeper
|
|
191
|
+
# only serves the document at the root well-known path, so such a value is
|
|
192
|
+
# not discoverable (and its advertised issuer would not match the lookup).
|
|
193
|
+
# This is a separate concern from validate_issuer_format: a path component
|
|
194
|
+
# is valid per RFC 8414, just unsupported by Doorkeeper's fixed route.
|
|
195
|
+
def validate_issuer_metadata_discoverability
|
|
196
|
+
return if issuer.blank?
|
|
197
|
+
|
|
198
|
+
uri =
|
|
199
|
+
begin
|
|
200
|
+
URI.parse(issuer.to_s)
|
|
201
|
+
rescue URI::InvalidURIError
|
|
202
|
+
nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
return if uri&.host.blank?
|
|
206
|
+
return if uri.path.blank? || uri.path == "/"
|
|
207
|
+
|
|
208
|
+
::Rails.logger.warn(
|
|
209
|
+
"[DOORKEEPER] issuer #{redacted_issuer.inspect} has a path component, but " \
|
|
210
|
+
"Doorkeeper serves its RFC 8414 metadata only at the root " \
|
|
211
|
+
"/.well-known/oauth-authorization-server. RFC 8414 clients derive the " \
|
|
212
|
+
"metadata URL from the issuer path (…/.well-known/" \
|
|
213
|
+
"oauth-authorization-server#{uri.path}), so they will not discover the " \
|
|
214
|
+
"document. Use a host-only issuer, or route the derived well-known path " \
|
|
215
|
+
"to Doorkeeper.",
|
|
216
|
+
)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Redact any userinfo (e.g. a misconfigured user:pass@host) before the
|
|
220
|
+
# issuer is written to the log, so credentials are not leaked there.
|
|
221
|
+
def redacted_issuer
|
|
222
|
+
issuer.to_s.sub(%r{//[^/@]*@}, "//***@")
|
|
223
|
+
end
|
|
63
224
|
end
|
|
64
225
|
end
|
|
65
226
|
end
|