doorkeeper 5.9.9 → 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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -22
  3. data/README.md +1 -0
  4. data/app/controllers/doorkeeper/authorizations_controller.rb +3 -33
  5. data/app/controllers/doorkeeper/authorized_applications_controller.rb +0 -23
  6. data/app/controllers/doorkeeper/metadata_controller.rb +20 -0
  7. data/app/controllers/doorkeeper/tokens_controller.rb +10 -1
  8. data/app/views/doorkeeper/authorizations/new.html.erb +6 -0
  9. data/lib/doorkeeper/client_authentication/credentials.rb +11 -0
  10. data/lib/doorkeeper/client_authentication/fallback_method.rb +19 -0
  11. data/lib/doorkeeper/client_authentication/legacy_callable.rb +46 -0
  12. data/lib/doorkeeper/client_authentication/method.rb +23 -0
  13. data/lib/doorkeeper/client_authentication/registry.rb +47 -0
  14. data/lib/doorkeeper/client_authentication.rb +93 -0
  15. data/lib/doorkeeper/config/option.rb +1 -1
  16. data/lib/doorkeeper/config/validations.rb +162 -1
  17. data/lib/doorkeeper/config.rb +114 -15
  18. data/lib/doorkeeper/errors.rb +1 -0
  19. data/lib/doorkeeper/models/access_token_mixin.rb +40 -5
  20. data/lib/doorkeeper/models/application_mixin.rb +14 -6
  21. data/lib/doorkeeper/models/concerns/secret_storable.rb +10 -1
  22. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +11 -0
  23. data/lib/doorkeeper/oauth/authorization_code_request.rb +4 -1
  24. data/lib/doorkeeper/oauth/client.rb +18 -0
  25. data/lib/doorkeeper/oauth/client_authentication/client_secret_basic.rb +53 -0
  26. data/lib/doorkeeper/oauth/client_authentication/client_secret_post.rb +29 -0
  27. data/lib/doorkeeper/oauth/client_authentication/none.rb +32 -0
  28. data/lib/doorkeeper/oauth/code_response.rb +16 -2
  29. data/lib/doorkeeper/oauth/error_response.rb +11 -2
  30. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +0 -14
  31. data/lib/doorkeeper/oauth/metadata_response.rb +157 -0
  32. data/lib/doorkeeper/oauth/pre_authorization.rb +12 -32
  33. data/lib/doorkeeper/oauth/token.rb +2 -95
  34. data/lib/doorkeeper/oauth/token_introspection.rb +27 -7
  35. data/lib/doorkeeper/orm/active_record/mixins/application.rb +2 -2
  36. data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +1 -5
  37. data/lib/doorkeeper/rails/routes/mapping.rb +1 -0
  38. data/lib/doorkeeper/rails/routes.rb +6 -0
  39. data/lib/doorkeeper/request.rb +65 -18
  40. data/lib/doorkeeper/server.rb +5 -2
  41. data/lib/doorkeeper/version.rb +4 -4
  42. data/lib/doorkeeper.rb +8 -4
  43. data/lib/generators/doorkeeper/templates/initializer.rb +67 -18
  44. metadata +14 -7
  45. data/lib/doorkeeper/oauth/client/credentials.rb +0 -86
@@ -3,30 +3,40 @@
3
3
  module Doorkeeper
4
4
  module Request
5
5
  class << self
6
- # Resolves the strategy that answers the given response_type at the
7
- # authorization endpoint.
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).
8
10
  #
9
- # A response_type no registered authorization flow handles is refused
10
- # rather than turned into a class name: `constantize` would otherwise
11
- # resolve any constant under Doorkeeper::Request, token endpoint
12
- # strategies (`password`, `client_credentials`, `refresh_token`,
13
- # `authorization_code`) included, none of which can answer an
14
- # authorization request.
15
- def authorization_strategy(response_type)
16
- response_type = response_type.to_s
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
17
27
 
28
+ def authorization_strategy(response_type)
18
29
  grant_flow = authorization_flows.detect do |flow|
19
30
  flow.matches_response_type?(response_type)
20
31
  end
21
32
 
22
- raise Errors::InvalidTokenStrategy unless grant_flow
23
-
24
- # [NOTE]: this will be removed in a newer versions of Doorkeeper.
25
- # For retro-compatibility only: flows declared through the deprecated
26
- # Config#calculate_authorization_response_types hook are built as
27
- # GrantFlow::FallbackFlow and carry no strategy, so the class is looked
28
- # up by name - but only for a response type the server does declare.
29
- grant_flow.response_type_strategy || build_fallback_strategy_class(response_type)
33
+ if grant_flow
34
+ grant_flow.response_type_strategy
35
+ else
36
+ # [NOTE]: this will be removed in a newer versions of Doorkeeper.
37
+ # For retro-compatibility only
38
+ build_fallback_strategy_class(response_type)
39
+ end
30
40
  end
31
41
 
32
42
  def token_strategy(grant_type)
@@ -52,6 +62,43 @@ module Doorkeeper
52
62
 
53
63
  private
54
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
+
55
102
  def authorization_flows
56
103
  Doorkeeper.configuration.authorization_response_flows
57
104
  end
@@ -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
- methods = Doorkeeper.config.client_credentials_methods
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
@@ -3,10 +3,10 @@
3
3
  module Doorkeeper
4
4
  module VERSION
5
5
  # Semantic versioning
6
- MAJOR = 5
7
- MINOR = 9
8
- TINY = 9
9
- PRE = nil
6
+ MAJOR = 6
7
+ MINOR = 0
8
+ TINY = 0
9
+ PRE = "beta1"
10
10
 
11
11
  # Full version number
12
12
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
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 non-confidential clients to use PKCE when using an authorization code
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,21 +286,18 @@ Doorkeeper.configure do
277
286
  #
278
287
  # enforce_configured_scopes
279
288
 
280
- # Change the way client credentials are retrieved from the request object.
281
- # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
282
- # falls back to the `:client_id` and `:client_secret` params from the `params` object.
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
- # NOTE: configuring a callable extractor opts the request out of the RFC 6749
287
- # §2.3 checks that reject a request authenticating the client with more than
288
- # one method, or presenting more than one client identity. A callable may
289
- # legitimately overlap the built-in extractors, and the extractors after the
290
- # matching one are never called, so what they would have returned is unknown.
291
- # Prefer the symbol extractors unless you need something they cannot express;
292
- # a callable has to reject such a request itself.
296
+ # client_authentication %i[client_secret_basic client_secret_post none]
293
297
  #
294
- # client_credentials :from_basic, :from_params
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`.
295
301
 
296
302
  # Change the way access token is authenticated from the request object.
297
303
  # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
@@ -316,13 +322,10 @@ Doorkeeper.configure do
316
322
  # Specify what redirect URI's you want to block during Application creation.
317
323
  # Any redirect URI is allowed by default.
318
324
  #
319
- # Redirect URIs with a script scheme (`javascript`, `vbscript`, `data`) are
320
- # always rejected, regardless of this option.
321
- #
322
- # You can use this option in order to forbid URI's of a particular host
325
+ # You can use this option in order to forbid URI's with 'javascript' scheme
323
326
  # for example.
324
327
  #
325
- # forbid_redirect_uri { |uri| uri.host == 'example.com' }
328
+ # forbid_redirect_uri { |uri| uri.scheme.to_s.downcase == 'javascript' }
326
329
 
327
330
  # Allows to set blank redirect URIs for Applications in case Doorkeeper configured
328
331
  # to use URI-less OAuth grant flows like Client Credentials or Resource Owner
@@ -388,6 +391,9 @@ Doorkeeper.configure do
388
391
  # If not specified, Doorkeeper enables authorization_code and
389
392
  # client_credentials.
390
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
+ #
391
397
  # implicit and password grant flows have risks that you should understand
392
398
  # before enabling:
393
399
  # https://datatracker.ietf.org/doc/html/rfc6819#section-4.4.2
@@ -552,4 +558,47 @@ Doorkeeper.configure do
552
558
  # WWW-Authenticate Realm (default: "Doorkeeper").
553
559
  #
554
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
+ # )
555
604
  end
metadata CHANGED
@@ -1,17 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.9.9
4
+ version: 6.0.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
8
8
  - Tute Costa
9
9
  - Jon Moss
10
10
  - Nikita Bulai
11
- autorequire:
12
11
  bindir: bin
13
12
  cert_chain: []
14
- date: 2026-09-24 00:00:00.000000000 Z
13
+ date: 1980-01-02 00:00:00.000000000 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: railties
@@ -184,6 +183,7 @@ files:
184
183
  - app/controllers/doorkeeper/applications_controller.rb
185
184
  - app/controllers/doorkeeper/authorizations_controller.rb
186
185
  - app/controllers/doorkeeper/authorized_applications_controller.rb
186
+ - app/controllers/doorkeeper/metadata_controller.rb
187
187
  - app/controllers/doorkeeper/token_info_controller.rb
188
188
  - app/controllers/doorkeeper/tokens_controller.rb
189
189
  - app/helpers/doorkeeper/dashboard_helper.rb
@@ -203,6 +203,12 @@ files:
203
203
  - app/views/layouts/doorkeeper/application.html.erb
204
204
  - config/locales/en.yml
205
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
206
212
  - lib/doorkeeper/config.rb
207
213
  - lib/doorkeeper/config/abstract_builder.rb
208
214
  - lib/doorkeeper/config/option.rb
@@ -240,7 +246,9 @@ files:
240
246
  - lib/doorkeeper/oauth/base_request.rb
241
247
  - lib/doorkeeper/oauth/base_response.rb
242
248
  - lib/doorkeeper/oauth/client.rb
243
- - lib/doorkeeper/oauth/client/credentials.rb
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
244
252
  - lib/doorkeeper/oauth/client_credentials/creator.rb
245
253
  - lib/doorkeeper/oauth/client_credentials/issuer.rb
246
254
  - lib/doorkeeper/oauth/client_credentials/validator.rb
@@ -256,6 +264,7 @@ files:
256
264
  - lib/doorkeeper/oauth/hooks/context.rb
257
265
  - lib/doorkeeper/oauth/invalid_request_response.rb
258
266
  - lib/doorkeeper/oauth/invalid_token_response.rb
267
+ - lib/doorkeeper/oauth/metadata_response.rb
259
268
  - lib/doorkeeper/oauth/nonstandard.rb
260
269
  - lib/doorkeeper/oauth/password_access_token_request.rb
261
270
  - lib/doorkeeper/oauth/pre_authorization.rb
@@ -330,7 +339,6 @@ metadata:
330
339
  bug_tracker_uri: https://github.com/doorkeeper-gem/doorkeeper/issues
331
340
  documentation_uri: https://doorkeeper.gitbook.io/guides/
332
341
  funding_uri: https://opencollective.com/doorkeeper-gem
333
- post_install_message:
334
342
  rdoc_options: []
335
343
  require_paths:
336
344
  - lib
@@ -345,8 +353,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
345
353
  - !ruby/object:Gem::Version
346
354
  version: '0'
347
355
  requirements: []
348
- rubygems_version: 3.5.16
349
- signing_key:
356
+ rubygems_version: 4.0.11
350
357
  specification_version: 4
351
358
  summary: OAuth 2 provider for Rails and Grape
352
359
  test_files: []
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Doorkeeper
4
- module OAuth
5
- class Client
6
- Credentials = Struct.new(:uid, :secret) do
7
- class << self
8
- # Extracts the client credentials from the request using the
9
- # configured extraction methods. The first method that yields
10
- # credentials wins, as it always has.
11
- #
12
- # Raises Errors::MultipleClientAuthMethods when the request
13
- # authenticates the client with more than one method, or presents
14
- # more than one client identity, which RFC 6749 §2.3 forbids ("The
15
- # client MUST NOT use more than one authentication method in each
16
- # request").
17
- def from_request(request, *credentials_methods)
18
- # Callable extractors are opaque: they may legitimately overlap the
19
- # built-in methods they are configured with, and evaluating all of
20
- # them would also break the existing contract that the extractors
21
- # after the matching one are not called. Such configurations keep
22
- # the historical behaviour untouched, including the fact that a
23
- # request authenticating with more than one method, or presenting
24
- # more than one client identity, resolves to the first extracted
25
- # credentials instead of being rejected: the extractor that would
26
- # have surfaced the second identity is never evaluated. The
27
- # +client_credentials+ configuration option documents this.
28
- return first_from_request(request, credentials_methods) unless credentials_methods.all?(Symbol)
29
-
30
- credentials = credentials_methods.filter_map { |method| extract(request, method) }
31
-
32
- # Only credentials carrying a secret authenticate the client. A bare
33
- # uid is a public client identifying itself (RFC 6749 §2.3 "none"),
34
- # not an authentication method of its own, so it doesn't count
35
- # towards the single-method rule — RFC 7521 §4.2, for example,
36
- # explicitly allows a client_id next to another authentication
37
- # method.
38
- raise Errors::MultipleClientAuthMethods if credentials.count { |c| c.secret.present? } > 1
39
-
40
- # §4.2 allows that bare client_id because it identifies the *same*
41
- # client as the authentication method it accompanies. Credentials
42
- # naming two different clients are two client identities in one
43
- # request and authenticate neither: without this check the first
44
- # extracted uid would win and the other identity would be silently
45
- # discarded, so the caller could authenticate as one client while
46
- # the request asks to act as another.
47
- raise Errors::MultipleClientAuthMethods if credentials.uniq(&:uid).size > 1
48
-
49
- credentials.first
50
- end
51
-
52
- def from_params(request)
53
- request.parameters.values_at(:client_id, :client_secret)
54
- end
55
-
56
- def from_basic(request)
57
- authorization = request.authorization
58
- if authorization.present? && authorization =~ /^Basic (.*)/im
59
- Base64.decode64(Regexp.last_match(1)).split(/:/, 2)
60
- end
61
- end
62
-
63
- private
64
-
65
- def first_from_request(request, credentials_methods)
66
- credentials_methods.inject(nil) do |_, method|
67
- credentials = extract(request, method)
68
- break credentials if credentials
69
- end
70
- end
71
-
72
- # Returns the credentials the given method extracts from the request,
73
- # or nil when it extracts none.
74
- def extract(request, method)
75
- method = self.method(method) if method.is_a?(Symbol)
76
- Credentials.new(*method.call(request)).presence
77
- end
78
- end
79
-
80
- # Public clients may have their secret blank, but "credentials" are
81
- # still present
82
- delegate :blank?, to: :uid
83
- end
84
- end
85
- end
86
- end