doorkeeper 5.4.0 → 5.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -1
  3. data/app/controllers/doorkeeper/authorizations_controller.rb +2 -1
  4. data/app/controllers/doorkeeper/tokens_controller.rb +3 -0
  5. data/config/locales/en.yml +0 -1
  6. data/lib/doorkeeper.rb +1 -0
  7. data/lib/doorkeeper/config.rb +79 -62
  8. data/lib/doorkeeper/config/option.rb +1 -3
  9. data/lib/doorkeeper/config/validations.rb +53 -0
  10. data/lib/doorkeeper/grant_flow.rb +43 -0
  11. data/lib/doorkeeper/grant_flow/fallback_flow.rb +15 -0
  12. data/lib/doorkeeper/grant_flow/flow.rb +34 -0
  13. data/lib/doorkeeper/grant_flow/registry.rb +50 -0
  14. data/lib/doorkeeper/helpers/controller.rb +2 -0
  15. data/lib/doorkeeper/models/access_token_mixin.rb +3 -3
  16. data/lib/doorkeeper/models/concerns/revocable.rb +1 -1
  17. data/lib/doorkeeper/oauth/authorization/context.rb +5 -5
  18. data/lib/doorkeeper/oauth/authorization/token.rb +6 -4
  19. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
  20. data/lib/doorkeeper/oauth/authorization_code_request.rb +10 -17
  21. data/lib/doorkeeper/oauth/base_request.rb +1 -1
  22. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  23. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +1 -0
  24. data/lib/doorkeeper/oauth/code_response.rb +22 -12
  25. data/lib/doorkeeper/oauth/error_response.rb +4 -3
  26. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -3
  27. data/lib/doorkeeper/oauth/password_access_token_request.rb +20 -1
  28. data/lib/doorkeeper/oauth/pre_authorization.rb +10 -6
  29. data/lib/doorkeeper/oauth/refresh_token_request.rb +13 -0
  30. data/lib/doorkeeper/rails/routes.rb +1 -1
  31. data/lib/doorkeeper/request.rb +49 -12
  32. data/lib/doorkeeper/version.rb +2 -2
  33. data/lib/generators/doorkeeper/templates/initializer.rb +9 -7
  34. metadata +11 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 76b3a86e21584548c9b0c176512c844bee90ba9c447aaf09741abf54488093bb
4
- data.tar.gz: ce7a4ffdf3b0aebaa69f703b70f0109276205c9ec0b2f1e2c7b3e88cb4746f8b
3
+ metadata.gz: 1099136de2b2fe0f0443f88bdbe2260ebed52efb2aab31d1d35bb7aa9f801acd
4
+ data.tar.gz: '09837820494b55d6bd26e2d997c203b221bc7951e74a6d1e2197122a86f0f1b2'
5
5
  SHA512:
6
- metadata.gz: 7192f9711713f15d323e85aa3ad4274b314a55dcc89ba945de52dca5dbbad2e267dc3252da353cd4991fae365a1161fd91d06c0bfcaba767163b4c54eafca125
7
- data.tar.gz: b5f324cfe8064b32254ca1c045bc24c54ab21a485bf3c6a9726bc995ab9dc24516872bf8ee314850b65f6ce3d879d0497e67416ea78c0f8f7566bdbfd48e024a
6
+ metadata.gz: 9197207fe8db140d8658aa12cd7422bfd84a001227a9f0841ebf5d92da4be531cfd7dea26aad584fff3573bc066b8fadc4e9d4e70bcc6dd5978253d94a9d66a4
7
+ data.tar.gz: 3b37c794027fcdbec12ef314bd6a927e111c2aed2044f3ead9f05893400e7221bf9adf98203efe7a225b395d5644326b943c144c13fb0caeccff5dff20a56ca5
@@ -7,7 +7,29 @@ User-visible changes worth mentioning.
7
7
 
8
8
  ## master
9
9
 
10
- - [#PR ID] Your PR description.
10
+ - [#PR ID] Add your PR description here.
11
+
12
+ ## 5.5.0.rc1
13
+
14
+ - [#1435] Make error response not redirectable when client is unauthorized
15
+ - [#1426] Ensure ActiveRecord callbacks are executed on token revocation.
16
+ - [#1407] Remove redundant and complex to support helpers froms tests (`should_have_json`, etc).
17
+ - [#1416] Don't add introspection route if token introspection completely disabled.
18
+ - [#1410] Properly memoize `current_resource_owner` value (consider `nil` and `false` values).
19
+ - [#1415] Ignore PKCE params for non-PKCE grants.
20
+ - [#1418] Add ability to register custom OAuth Grant Flows.
21
+ - [#1420] Require client authentication for Resource Owner Password Grant as stated in OAuth RFC.
22
+
23
+ **[IMPORTANT]** you need to create a new OAuth client (`Doorkeeper::Application`) if yoo didn't
24
+ have it before and use client credentials in HTTP Basic auth if you previously used this grant
25
+ flow without client authentication. For migration purposes you could enable
26
+ `skip_client_authentication_for_password_grant` configuration option to `true`, but such behavior
27
+ (as well as configuration option) would be completely removed in a future version of Doorkeeper.
28
+ All the users of your provider application now need to include client credentials when they use
29
+ this grant flow.
30
+
31
+ - [#1421] Add Resource Owner instance to authorization hook context for `custom_access_token_expires_in`
32
+ configuration option to allow resource owner based Access Tokens TTL.
11
33
 
12
34
  ## 5.4.0
13
35
 
@@ -83,7 +83,8 @@ module Doorkeeper
83
83
  code_challenge_method
84
84
  response_type
85
85
  redirect_uri
86
- scope state
86
+ scope
87
+ state
87
88
  ]
88
89
  end
89
90
 
@@ -69,6 +69,9 @@ module Doorkeeper
69
69
  private
70
70
 
71
71
  # OAuth 2.0 Section 2.1 defines two client types, "public" & "confidential".
72
+ #
73
+ # RFC7009
74
+ # Section 5. Security Considerations
72
75
  # A malicious client may attempt to guess valid tokens on this endpoint
73
76
  # by making revocation requests against potential token strings.
74
77
  # According to this specification, a client's request must contain a
@@ -93,7 +93,6 @@ en:
93
93
  invalid_request:
94
94
  unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
95
95
  missing_param: 'Missing required parameter: %{value}.'
96
- not_support_pkce: 'Invalid code_verifier parameter. Server does not support pkce.'
97
96
  request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
98
97
  invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
99
98
  unauthorized_client: 'The client is not authorized to perform this request using this method.'
@@ -7,6 +7,7 @@ require "doorkeeper/engine"
7
7
  #
8
8
  module Doorkeeper
9
9
  autoload :Errors, "doorkeeper/errors"
10
+ autoload :GrantFlow, "doorkeeper/grant_flow"
10
11
  autoload :OAuth, "doorkeeper/oauth"
11
12
  autoload :Rake, "doorkeeper/rake"
12
13
  autoload :Request, "doorkeeper/request"
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "doorkeeper/config/option"
4
3
  require "doorkeeper/config/abstract_builder"
4
+ require "doorkeeper/config/option"
5
+ require "doorkeeper/config/validations"
5
6
 
6
7
  module Doorkeeper
7
8
  # Defines a MissingConfiguration error for a missing Doorkeeper configuration
@@ -219,6 +220,7 @@ module Doorkeeper
219
220
  mattr_reader(:builder_class) { Builder }
220
221
 
221
222
  extend Option
223
+ include Validations
222
224
 
223
225
  option :resource_owner_authenticator,
224
226
  as: :authenticate_resource_owner,
@@ -267,7 +269,6 @@ module Doorkeeper
267
269
  option :grant_flows, default: %w[authorization_code client_credentials]
268
270
  option :handle_auth_errors, default: :render
269
271
  option :token_lookup_batch_size, default: 10_000
270
-
271
272
  # Sets the token_reuse_limit
272
273
  # It will be used only when reuse_access_token option in enabled
273
274
  # By default it will be 100
@@ -275,6 +276,11 @@ module Doorkeeper
275
276
  # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1189
276
277
  option :token_reuse_limit, default: 100
277
278
 
279
+ # [NOTE]: will be removed in a future version of Doorkeeper
280
+ option :skip_client_authentication_for_password_grant,
281
+ default: false,
282
+ deprecated: { message: "OAuth RFC requires client authentication so you need at least to create one" }
283
+
278
284
  option :active_record_options,
279
285
  default: {},
280
286
  deprecated: { message: "Customize Doorkeeper models instead" }
@@ -423,13 +429,6 @@ module Doorkeeper
423
429
  :token_secret_fallback_strategy,
424
430
  :application_secret_fallback_strategy
425
431
 
426
- # Return the valid subset of this configuration
427
- def validate!
428
- validate_reuse_access_token_value
429
- validate_token_reuse_limit
430
- validate_secret_strategies
431
- end
432
-
433
432
  # Doorkeeper Access Token model class.
434
433
  #
435
434
  # @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model]
@@ -545,12 +544,77 @@ module Doorkeeper
545
544
  ]
546
545
  end
547
546
 
547
+ def enabled_grant_flows
548
+ @enabled_grant_flows ||= calculate_grant_flows.map { |name| Doorkeeper::GrantFlow.get(name) }.compact
549
+ end
550
+
551
+ def authorization_response_flows
552
+ @authorization_response_flows ||= enabled_grant_flows.select(&:handles_response_type?) +
553
+ deprecated_authorization_flows
554
+ end
555
+
556
+ def token_grant_flows
557
+ @token_grant_flows ||= calculate_token_grant_flows
558
+ end
559
+
548
560
  def authorization_response_types
549
- @authorization_response_types ||= calculate_authorization_response_types.freeze
561
+ authorization_response_flows.map(&:response_type_matches)
550
562
  end
551
563
 
552
564
  def token_grant_types
553
- @token_grant_types ||= calculate_token_grant_types.freeze
565
+ token_grant_flows.map(&:grant_type_matches)
566
+ end
567
+
568
+ # [NOTE]: deprecated and will be removed soon
569
+ def deprecated_token_grant_types_resolver
570
+ @deprecated_token_grant_types ||= calculate_token_grant_types
571
+ end
572
+
573
+ # [NOTE]: deprecated and will be removed soon
574
+ def deprecated_authorization_flows
575
+ response_types = calculate_authorization_response_types
576
+
577
+ if response_types.any?
578
+ ::Kernel.warn <<~WARNING
579
+ Please, don't patch Doorkeeper::Config#calculate_authorization_response_types method.
580
+ Register your custom grant flows using the public API:
581
+ `Doorkeeper::GrantFlow.register(grant_flow_name, **options)`.
582
+ WARNING
583
+ end
584
+
585
+ response_types.map do |response_type|
586
+ Doorkeeper::GrantFlow::FallbackFlow.new(response_type, response_type_matches: response_type)
587
+ end
588
+ end
589
+
590
+ # [NOTE]: deprecated and will be removed soon
591
+ def calculate_authorization_response_types
592
+ []
593
+ end
594
+
595
+ # [NOTE]: deprecated and will be removed soon
596
+ def calculate_token_grant_types
597
+ types = grant_flows - ["implicit"]
598
+ types << "refresh_token" if refresh_token_enabled?
599
+ types
600
+ end
601
+
602
+ # Calculates grant flows configured by the user in Doorkeeper
603
+ # configuration considering registered aliases that is exposed
604
+ # to single or multiple other flows.
605
+ #
606
+ def calculate_grant_flows
607
+ configured_flows = grant_flows.map(&:to_s)
608
+ aliases = Doorkeeper::GrantFlow.aliases.keys.map(&:to_s)
609
+
610
+ flows = configured_flows - aliases
611
+ aliases.each do |flow_alias|
612
+ next unless configured_flows.include?(flow_alias)
613
+
614
+ flows.concat(Doorkeeper::GrantFlow.expand_alias(flow_alias))
615
+ end
616
+
617
+ flows.flatten.uniq
554
618
  end
555
619
 
556
620
  def allow_blank_redirect_uri?(application = nil)
@@ -579,57 +643,10 @@ module Doorkeeper
579
643
  !!(defined?(var) && var)
580
644
  end
581
645
 
582
- # Determines what values are acceptable for 'response_type' param in
583
- # authorization request endpoint, and return them as an array of strings.
584
- #
585
- def calculate_authorization_response_types
586
- types = []
587
- types << "code" if grant_flows.include? "authorization_code"
588
- types << "token" if grant_flows.include? "implicit"
589
- types
590
- end
591
-
592
- # Determines what values are acceptable for 'grant_type' param token
593
- # request endpoint, and return them in array.
594
- #
595
- def calculate_token_grant_types
596
- types = grant_flows - ["implicit"]
597
- types << "refresh_token" if refresh_token_enabled?
598
- types
599
- end
600
-
601
- # Determine whether +reuse_access_token+ and a non-restorable
602
- # +token_secret_strategy+ have both been activated.
603
- #
604
- # In that case, disable reuse_access_token value and warn the user.
605
- def validate_reuse_access_token_value
606
- strategy = token_secret_strategy
607
- return if !reuse_access_token || strategy.allows_restoring_secrets?
608
-
609
- ::Rails.logger.warn(
610
- "You have configured both reuse_access_token " \
611
- "AND strategy strategy '#{strategy}' that cannot restore tokens. " \
612
- "This combination is unsupported. reuse_access_token will be disabled",
613
- )
614
- @reuse_access_token = false
615
- end
616
-
617
- # Validate that the provided strategies are valid for
618
- # tokens and applications
619
- def validate_secret_strategies
620
- token_secret_strategy.validate_for :token
621
- application_secret_strategy.validate_for :application
622
- end
623
-
624
- def validate_token_reuse_limit
625
- return if !reuse_access_token ||
626
- (token_reuse_limit > 0 && token_reuse_limit <= 100)
627
-
628
- ::Rails.logger.warn(
629
- "You have configured an invalid value for token_reuse_limit option. " \
630
- "It will be set to default 100",
631
- )
632
- @token_reuse_limit = 100
646
+ def calculate_token_grant_flows
647
+ flows = enabled_grant_flows.select(&:handles_grant_type?)
648
+ flows << Doorkeeper::GrantFlow.get("refresh_token") if refresh_token_enabled?
649
+ flows
633
650
  end
634
651
  end
635
652
  end
@@ -45,9 +45,7 @@ module Doorkeeper
45
45
  define_method name do |*args, &block|
46
46
  if (deprecation_opts = options[:deprecated])
47
47
  warning = "[DOORKEEPER] #{name} has been deprecated and will soon be removed"
48
- if deprecation_opts.is_a?(Hash)
49
- warning = "#{warning}\n#{deprecation_opts.fetch(:message)}"
50
- end
48
+ warning = "#{warning}\n#{deprecation_opts.fetch(:message)}" if deprecation_opts.is_a?(Hash)
51
49
 
52
50
  Kernel.warn(warning)
53
51
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ class Config
5
+ # Doorkeeper configuration validator.
6
+ #
7
+ module Validations
8
+ # Validates configuration options to be set properly.
9
+ #
10
+ def validate!
11
+ validate_reuse_access_token_value
12
+ validate_token_reuse_limit
13
+ validate_secret_strategies
14
+ end
15
+
16
+ private
17
+
18
+ # Determine whether +reuse_access_token+ and a non-restorable
19
+ # +token_secret_strategy+ have both been activated.
20
+ #
21
+ # In that case, disable reuse_access_token value and warn the user.
22
+ def validate_reuse_access_token_value
23
+ strategy = token_secret_strategy
24
+ return if !reuse_access_token || strategy.allows_restoring_secrets?
25
+
26
+ ::Rails.logger.warn(
27
+ "You have configured both reuse_access_token " \
28
+ "AND strategy strategy '#{strategy}' that cannot restore tokens. " \
29
+ "This combination is unsupported. reuse_access_token will be disabled",
30
+ )
31
+ @reuse_access_token = false
32
+ end
33
+
34
+ # Validate that the provided strategies are valid for
35
+ # tokens and applications
36
+ def validate_secret_strategies
37
+ token_secret_strategy.validate_for(:token)
38
+ application_secret_strategy.validate_for(:application)
39
+ end
40
+
41
+ def validate_token_reuse_limit
42
+ return if !reuse_access_token ||
43
+ (token_reuse_limit > 0 && token_reuse_limit <= 100)
44
+
45
+ ::Rails.logger.warn(
46
+ "You have configured an invalid value for token_reuse_limit option. " \
47
+ "It will be set to default 100",
48
+ )
49
+ @token_reuse_limit = 100
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "doorkeeper/grant_flow/flow"
4
+ require "doorkeeper/grant_flow/fallback_flow"
5
+ require "doorkeeper/grant_flow/registry"
6
+
7
+ module Doorkeeper
8
+ module GrantFlow
9
+ extend Registry
10
+
11
+ register(
12
+ :implicit,
13
+ response_type_matches: "token",
14
+ response_type_strategy: Doorkeeper::Request::Token,
15
+ )
16
+
17
+ register(
18
+ :authorization_code,
19
+ response_type_matches: "code",
20
+ response_type_strategy: Doorkeeper::Request::Code,
21
+ grant_type_matches: "authorization_code",
22
+ grant_type_strategy: Doorkeeper::Request::AuthorizationCode,
23
+ )
24
+
25
+ register(
26
+ :client_credentials,
27
+ grant_type_matches: "client_credentials",
28
+ grant_type_strategy: Doorkeeper::Request::ClientCredentials,
29
+ )
30
+
31
+ register(
32
+ :password,
33
+ grant_type_matches: "password",
34
+ grant_type_strategy: Doorkeeper::Request::Password,
35
+ )
36
+
37
+ register(
38
+ :refresh_token,
39
+ grant_type_matches: "refresh_token",
40
+ grant_type_strategy: Doorkeeper::Request::RefreshToken,
41
+ )
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module GrantFlow
5
+ class FallbackFlow < Flow
6
+ def handles_grant_type?
7
+ false
8
+ end
9
+
10
+ def handles_response_type?
11
+ false
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module GrantFlow
5
+ class Flow
6
+ attr_reader :name, :grant_type_matches, :grant_type_strategy,
7
+ :response_type_matches, :response_type_strategy
8
+
9
+ def initialize(name, **options)
10
+ @name = name
11
+ @grant_type_matches = options[:grant_type_matches]
12
+ @grant_type_strategy = options[:grant_type_strategy]
13
+ @response_type_matches = options[:response_type_matches]
14
+ @response_type_strategy = options[:response_type_strategy]
15
+ end
16
+
17
+ def handles_grant_type?
18
+ grant_type_matches.present?
19
+ end
20
+
21
+ def handles_response_type?
22
+ response_type_matches.present?
23
+ end
24
+
25
+ def matches_grant_type?(value)
26
+ grant_type_matches === value
27
+ end
28
+
29
+ def matches_response_type?(value)
30
+ response_type_matches === value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module GrantFlow
5
+ module Registry
6
+ mattr_accessor :flows
7
+ self.flows = {}
8
+
9
+ mattr_accessor :aliases
10
+ self.aliases = {}
11
+
12
+ # Allows to register custom OAuth grant flow so that Doorkeeper
13
+ # could recognize and process it.
14
+ #
15
+ def register(name_or_flow, **options)
16
+ unless name_or_flow.is_a?(Doorkeeper::GrantFlow::Flow)
17
+ name_or_flow = Flow.new(name_or_flow, **options)
18
+ end
19
+
20
+ flow_key = name_or_flow.name.to_sym
21
+
22
+ if flows.key?(flow_key)
23
+ ::Kernel.warn <<~WARNING
24
+ [DOORKEEPER] '#{flow_key}' grant flow already registered and will be overridden
25
+ in #{caller(1..1).first}
26
+ WARNING
27
+ end
28
+
29
+ flows[flow_key] = name_or_flow
30
+ end
31
+
32
+ # Allows to register aliases that could be used in `grant_flows`
33
+ # configuration option. It is possible to have aliases like 1:1 or
34
+ # 1:N, i.e. "implicit_oidc" => ['token', 'id_token', 'id_token token'].
35
+ #
36
+ def register_alias(alias_name, **options)
37
+ aliases[alias_name.to_sym] = Array.wrap(options.fetch(:as))
38
+ end
39
+
40
+ def expand_alias(alias_name)
41
+ aliases.fetch(alias_name.to_sym, [])
42
+ end
43
+
44
+ # [NOTE]: make it to use #fetch after removing fallbacks
45
+ def get(name)
46
+ flows[name.to_sym]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -16,6 +16,8 @@ module Doorkeeper
16
16
 
17
17
  # :doc:
18
18
  def current_resource_owner
19
+ return @current_resource_owner if defined?(@current_resource_owner)
20
+
19
21
  @current_resource_owner ||= begin
20
22
  instance_eval(&Doorkeeper.config.authenticate_resource_owner)
21
23
  end
@@ -94,8 +94,8 @@ module Doorkeeper
94
94
  # Interface to enumerate access token records in batches in order not
95
95
  # to bloat the memory. Could be overloaded in any ORM extension.
96
96
  #
97
- def find_access_token_in_batches(relation, *args, &block)
98
- relation.find_in_batches(*args, &block)
97
+ def find_access_token_in_batches(relation, **args, &block)
98
+ relation.find_in_batches(**args, &block)
99
99
  end
100
100
 
101
101
  # Enumerates AccessToken records in batches to find a matching token.
@@ -377,7 +377,7 @@ module Doorkeeper
377
377
  return unless self.class.refresh_token_revoked_on_use?
378
378
 
379
379
  old_refresh_token&.revoke
380
- update_column(:previous_refresh_token, "")
380
+ update_attribute(:previous_refresh_token, "")
381
381
  end
382
382
 
383
383
  private
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
  # @param clock [Time] time object
10
10
  #
11
11
  def revoke(clock = Time)
12
- update_column(:revoked_at, clock.now.utc)
12
+ update_attribute(:revoked_at, clock.now.utc)
13
13
  end
14
14
 
15
15
  # Indicates whether the object has been revoked.
@@ -4,12 +4,12 @@ module Doorkeeper
4
4
  module OAuth
5
5
  module Authorization
6
6
  class Context
7
- attr_reader :client, :grant_type, :scopes
7
+ attr_reader :client, :grant_type, :resource_owner, :scopes
8
8
 
9
- def initialize(client, grant_type, scopes)
10
- @client = client
11
- @grant_type = grant_type
12
- @scopes = scopes
9
+ def initialize(**attributes)
10
+ attributes.each do |name, value|
11
+ instance_variable_set(:"@#{name}", value) if respond_to?(name)
12
+ end
13
13
  end
14
14
  end
15
15
  end
@@ -7,7 +7,7 @@ module Doorkeeper
7
7
  attr_reader :pre_auth, :resource_owner, :token
8
8
 
9
9
  class << self
10
- def build_context(pre_auth_or_oauth_client, grant_type, scopes)
10
+ def build_context(pre_auth_or_oauth_client, grant_type, scopes, resource_owner)
11
11
  oauth_client = if pre_auth_or_oauth_client.respond_to?(:application)
12
12
  pre_auth_or_oauth_client.application
13
13
  elsif pre_auth_or_oauth_client.respond_to?(:client)
@@ -17,9 +17,10 @@ module Doorkeeper
17
17
  end
18
18
 
19
19
  Doorkeeper::OAuth::Authorization::Context.new(
20
- oauth_client,
21
- grant_type,
22
- scopes,
20
+ client: oauth_client,
21
+ grant_type: grant_type,
22
+ scopes: scopes,
23
+ resource_owner: resource_owner,
23
24
  )
24
25
  end
25
26
 
@@ -55,6 +56,7 @@ module Doorkeeper
55
56
  pre_auth.client,
56
57
  Doorkeeper::OAuth::IMPLICIT,
57
58
  pre_auth.scopes,
59
+ resource_owner,
58
60
  )
59
61
 
60
62
  @token = Doorkeeper.config.access_token_model.find_or_create_for(
@@ -23,7 +23,7 @@ module Doorkeeper
23
23
  private
24
24
 
25
25
  def build_query(parameters = {})
26
- parameters = parameters.reject { |_, value| value.blank? }
26
+ parameters.reject! { |_, value| value.blank? }
27
27
  Rack::Utils.build_query(parameters)
28
28
  end
29
29
  end
@@ -3,7 +3,6 @@
3
3
  module Doorkeeper
4
4
  module OAuth
5
5
  class AuthorizationCodeRequest < BaseRequest
6
- validate :pkce_support, error: :invalid_request
7
6
  validate :params, error: :invalid_request
8
7
  validate :client, error: :invalid_client
9
8
  validate :grant, error: :invalid_grant
@@ -32,12 +31,6 @@ module Doorkeeper
32
31
 
33
32
  grant.revoke
34
33
 
35
- resource_owner = if Doorkeeper.config.polymorphic_resource_owner?
36
- grant.resource_owner
37
- else
38
- grant.resource_owner_id
39
- end
40
-
41
34
  find_or_create_access_token(
42
35
  grant.application,
43
36
  resource_owner,
@@ -49,16 +42,16 @@ module Doorkeeper
49
42
  super
50
43
  end
51
44
 
52
- def pkce_supported?
53
- Doorkeeper.config.access_grant_model.pkce_supported?
45
+ def resource_owner
46
+ if Doorkeeper.config.polymorphic_resource_owner?
47
+ grant.resource_owner
48
+ else
49
+ grant.resource_owner_id
50
+ end
54
51
  end
55
52
 
56
- def validate_pkce_support
57
- @invalid_request_reason = :not_support_pkce if grant &&
58
- !pkce_supported? &&
59
- code_verifier.present?
60
-
61
- @invalid_request_reason.nil?
53
+ def pkce_supported?
54
+ Doorkeeper.config.access_grant_model.pkce_supported?
62
55
  end
63
56
 
64
57
  def validate_params
@@ -91,8 +84,8 @@ module Doorkeeper
91
84
  # if either side (server or client) request PKCE, check the verifier
92
85
  # against the DB - if PKCE is supported
93
86
  def validate_code_verifier
94
- return true unless grant.uses_pkce? || code_verifier
95
- return false unless pkce_supported?
87
+ return true unless pkce_supported?
88
+ return grant.code_challenge.blank? if code_verifier.blank?
96
89
 
97
90
  if grant.code_challenge_method == "S256"
98
91
  grant.code_challenge == generate_code_challenge(code_verifier)
@@ -27,7 +27,7 @@ module Doorkeeper
27
27
  end
28
28
 
29
29
  def find_or_create_access_token(client, resource_owner, scopes, server)
30
- context = Authorization::Token.build_context(client, grant_type, scopes)
30
+ context = Authorization::Token.build_context(client, grant_type, scopes, resource_owner)
31
31
  @access_token = server_config.access_token_model.find_or_create_for(
32
32
  application: client,
33
33
  resource_owner: resource_owner,
@@ -39,7 +39,8 @@ module Doorkeeper
39
39
  end
40
40
 
41
41
  def lookup_existing_token?
42
- server_config.reuse_access_token || server_config.revoke_previous_client_credentials_token?
42
+ server_config.reuse_access_token ||
43
+ server_config.revoke_previous_client_credentials_token?
43
44
  end
44
45
 
45
46
  def find_existing_token_for(client, scopes)
@@ -30,6 +30,7 @@ module Doorkeeper
30
30
  client,
31
31
  Doorkeeper::OAuth::CLIENT_CREDENTIALS,
32
32
  scopes,
33
+ nil,
33
34
  )
34
35
  ttl = Authorization::Token.access_token_expires_in(@server, context)
35
36
 
@@ -25,21 +25,31 @@ module Doorkeeper
25
25
  if URIChecker.oob_uri?(pre_auth.redirect_uri)
26
26
  auth.oob_redirect
27
27
  elsif response_on_fragment
28
- Authorization::URIBuilder.uri_with_fragment(
29
- pre_auth.redirect_uri,
30
- access_token: auth.token.plaintext_token,
31
- token_type: auth.token.token_type,
32
- expires_in: auth.token.expires_in_seconds,
33
- state: pre_auth.state,
34
- )
28
+ uri_with_fragment
35
29
  else
36
- Authorization::URIBuilder.uri_with_query(
37
- pre_auth.redirect_uri,
38
- code: auth.token.plaintext_token,
39
- state: pre_auth.state,
40
- )
30
+ uri_with_query
41
31
  end
42
32
  end
33
+
34
+ private
35
+
36
+ def uri_with_fragment
37
+ Authorization::URIBuilder.uri_with_fragment(
38
+ pre_auth.redirect_uri,
39
+ access_token: auth.token.plaintext_token,
40
+ token_type: auth.token.token_type,
41
+ expires_in: auth.token.expires_in_seconds,
42
+ state: pre_auth.state,
43
+ )
44
+ end
45
+
46
+ def uri_with_query
47
+ Authorization::URIBuilder.uri_with_query(
48
+ pre_auth.redirect_uri,
49
+ code: auth.token.plaintext_token,
50
+ state: pre_auth.state,
51
+ )
52
+ end
43
53
  end
44
54
  end
45
55
  end
@@ -5,6 +5,8 @@ module Doorkeeper
5
5
  class ErrorResponse < BaseResponse
6
6
  include OAuth::Helpers
7
7
 
8
+ NON_REDIRECTABLE_STATES = %i[invalid_redirect_uri invalid_client unauthorized_client].freeze
9
+
8
10
  def self.from_request(request, attributes = {})
9
11
  new(
10
12
  attributes.merge(
@@ -32,7 +34,7 @@ module Doorkeeper
32
34
  end
33
35
 
34
36
  def status
35
- if name == :invalid_client
37
+ if name == :invalid_client || name == :unauthorized_client
36
38
  :unauthorized
37
39
  else
38
40
  :bad_request
@@ -40,8 +42,7 @@ module Doorkeeper
40
42
  end
41
43
 
42
44
  def redirectable?
43
- name != :invalid_redirect_uri && name != :invalid_client &&
44
- !URIChecker.oob_uri?(@redirect_uri)
45
+ !NON_REDIRECTABLE_STATES.include?(name) && !URIChecker.oob_uri?(@redirect_uri)
45
46
  end
46
47
 
47
48
  def redirect_uri
@@ -12,9 +12,7 @@ module Doorkeeper
12
12
  @scope_str = scope_str
13
13
  @valid_scopes = valid_scopes(server_scopes, app_scopes)
14
14
 
15
- if grant_type
16
- @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym]
17
- end
15
+ @scopes_by_grant_type = Doorkeeper.config.scopes_by_grant_type[grant_type.to_sym] if grant_type
18
16
  end
19
17
 
20
18
  def valid?
@@ -43,8 +43,27 @@ module Doorkeeper
43
43
  resource_owner.present?
44
44
  end
45
45
 
46
+ # Section 4.3.2. Access Token Request for Resource Owner Password Credentials Grant:
47
+ #
48
+ # If the client type is confidential or the client was issued client credentials (or assigned
49
+ # other authentication requirements), the client MUST authenticate with the authorization
50
+ # server as described in Section 3.2.1.
51
+ #
52
+ # The authorization server MUST:
53
+ #
54
+ # o require client authentication for confidential clients or for any client that was
55
+ # issued client credentials (or with other authentication requirements)
56
+ #
57
+ # o authenticate the client if client authentication is included,
58
+ #
59
+ # @see https://tools.ietf.org/html/rfc6749#section-4.3
60
+ #
46
61
  def validate_client
47
- !parameters[:client_id] || client.present?
62
+ if Doorkeeper.config.skip_client_authentication_for_password_grant
63
+ !parameters[:client_id] || client.present?
64
+ else
65
+ client.present?
66
+ end
48
67
  end
49
68
 
50
69
  def validate_client_supports_grant_flow
@@ -84,6 +84,11 @@ module Doorkeeper
84
84
  Doorkeeper.config.allow_grant_flow_for_client?(grant_type, client.application)
85
85
  end
86
86
 
87
+ def validate_resource_owner_authorize_for_client
88
+ # The `authorize_resource_owner_for_client` config option is used for this validation
89
+ client.application.authorized_for_resource_owner?(@resource_owner)
90
+ end
91
+
87
92
  def validate_redirect_uri
88
93
  return false if redirect_uri.blank?
89
94
 
@@ -104,7 +109,9 @@ module Doorkeeper
104
109
  end
105
110
 
106
111
  def validate_response_type
107
- server.authorization_response_types.include?(response_type)
112
+ server.authorization_response_flows.any? do |flow|
113
+ flow.matches_response_type?(response_type)
114
+ end
108
115
  end
109
116
 
110
117
  def validate_scopes
@@ -117,15 +124,12 @@ module Doorkeeper
117
124
  end
118
125
 
119
126
  def validate_code_challenge_method
127
+ return true unless Doorkeeper.config.access_grant_model.pkce_supported?
128
+
120
129
  code_challenge.blank? ||
121
130
  (code_challenge_method.present? && code_challenge_method =~ /^plain$|^S256$/)
122
131
  end
123
132
 
124
- def validate_resource_owner_authorize_for_client
125
- # The `authorize_resource_owner_for_client` config option is used for this validation
126
- client.application.authorized_for_resource_owner?(@resource_owner)
127
- end
128
-
129
133
  def response_on_fragment?
130
134
  response_type == "token"
131
135
  end
@@ -62,6 +62,19 @@ module Doorkeeper
62
62
  attributes[:previous_refresh_token] = refresh_token.refresh_token
63
63
  end
64
64
 
65
+ # RFC6749
66
+ # 1.5. Refresh Token
67
+ #
68
+ # Refresh tokens are issued to the client by the authorization server and are
69
+ # used to obtain a new access token when the current access token
70
+ # becomes invalid or expires, or to obtain additional access tokens
71
+ # with identical or narrower scope (access tokens may have a shorter
72
+ # lifetime and fewer permissions than authorized by the resource
73
+ # owner).
74
+ #
75
+ # Here we assume that TTL of the token received after refreshing should be
76
+ # the same as that of the original token.
77
+ #
65
78
  @access_token = server_config.access_token_model.create_for(
66
79
  application: refresh_token.application,
67
80
  resource_owner: resource_owner,
@@ -38,7 +38,7 @@ module Doorkeeper
38
38
  map_route(:authorizations, :authorization_routes)
39
39
  map_route(:tokens, :token_routes)
40
40
  map_route(:tokens, :revoke_routes)
41
- map_route(:tokens, :introspect_routes)
41
+ map_route(:tokens, :introspect_routes) unless Doorkeeper.config.allow_token_introspection.is_a?(FalseClass)
42
42
  map_route(:applications, :application_routes)
43
43
  map_route(:authorized_applications, :authorized_applications_routes)
44
44
  map_route(:token_info, :token_info_routes)
@@ -4,32 +4,69 @@ module Doorkeeper
4
4
  module Request
5
5
  class << self
6
6
  def authorization_strategy(response_type)
7
- build_strategy_class(response_type)
7
+ grant_flow = authorization_flows.detect do |flow|
8
+ flow.matches_response_type?(response_type)
9
+ end
10
+
11
+ if grant_flow
12
+ grant_flow.response_type_strategy
13
+ else
14
+ # [NOTE]: this will be removed in a newer versions of Doorkeeper.
15
+ # For retro-compatibility only
16
+ build_fallback_strategy_class(response_type)
17
+ end
8
18
  end
9
19
 
10
20
  def token_strategy(grant_type)
11
21
  raise Errors::MissingRequiredParameter, :grant_type if grant_type.blank?
12
22
 
13
- get_strategy(grant_type, token_grant_types)
14
- rescue NameError
15
- raise Errors::InvalidTokenStrategy
16
- end
23
+ grant_flow = token_flows.detect do |flow|
24
+ flow.matches_grant_type?(grant_type)
25
+ end
17
26
 
18
- def get_strategy(grant_type, available)
19
- raise NameError unless available.include?(grant_type.to_s)
27
+ if grant_flow
28
+ grant_flow.grant_type_strategy
29
+ else
30
+ # [NOTE]: this will be removed in a newer versions of Doorkeeper.
31
+ # For retro-compatibility only
32
+ raise Errors::InvalidTokenStrategy unless available.include?(grant_type.to_s)
20
33
 
21
- build_strategy_class(grant_type)
34
+ strategy_class = build_fallback_strategy_class(grant_type)
35
+ raise Errors::InvalidTokenStrategy unless strategy_class
36
+
37
+ strategy_class
38
+ end
22
39
  end
23
40
 
24
41
  private
25
42
 
26
- def token_grant_types
27
- Doorkeeper.config.token_grant_types
43
+ def authorization_flows
44
+ Doorkeeper.configuration.authorization_response_flows
45
+ end
46
+
47
+ def token_flows
48
+ Doorkeeper.configuration.token_grant_flows
28
49
  end
29
50
 
30
- def build_strategy_class(grant_or_request_type)
51
+ # [NOTE]: this will be removed in a newer versions of Doorkeeper.
52
+ # For retro-compatibility only
53
+ def available
54
+ Doorkeeper.config.deprecated_token_grant_types_resolver
55
+ end
56
+
57
+ def build_fallback_strategy_class(grant_or_request_type)
31
58
  strategy_class_name = grant_or_request_type.to_s.tr(" ", "_").camelize
32
- "Doorkeeper::Request::#{strategy_class_name}".constantize
59
+ fallback_strategy = "Doorkeeper::Request::#{strategy_class_name}".constantize
60
+
61
+ ::Kernel.warn <<~WARNING
62
+ [DOORKEEPER] #{fallback_strategy} found using fallback, it must be
63
+ registered using `Doorkeeper::GrantFlow.register(grant_flow_name, **options)`.
64
+ This functionality will be removed in a newer versions of Doorkeeper.
65
+ WARNING
66
+
67
+ fallback_strategy
68
+ rescue NameError
69
+ raise Errors::InvalidTokenStrategy
33
70
  end
34
71
  end
35
72
  end
@@ -8,9 +8,9 @@ module Doorkeeper
8
8
  module VERSION
9
9
  # Semantic versioning
10
10
  MAJOR = 5
11
- MINOR = 4
11
+ MINOR = 5
12
12
  TINY = 0
13
- PRE = nil
13
+ PRE = "rc1"
14
14
 
15
15
  # Full version number
16
16
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -103,12 +103,13 @@ Doorkeeper.configure do
103
103
  #
104
104
  # `context` has the following properties available:
105
105
  #
106
- # `client` - the OAuth client application (see Doorkeeper::OAuth::Client)
107
- # `grant_type` - the grant type of the request (see Doorkeeper::OAuth)
108
- # `scopes` - the requested scopes (see Doorkeeper::OAuth::Scopes)
106
+ # * `client` - the OAuth client application (see Doorkeeper::OAuth::Client)
107
+ # * `grant_type` - the grant type of the request (see Doorkeeper::OAuth)
108
+ # * `scopes` - the requested scopes (see Doorkeeper::OAuth::Scopes)
109
+ # * `resource_owner` - authorized resource owner instance (if present)
109
110
  #
110
111
  # custom_access_token_expires_in do |context|
111
- # context.client.application.additional_settings.implicit_oauth_expiration
112
+ # context.client.additional_settings.implicit_oauth_expiration
112
113
  # end
113
114
 
114
115
  # Use a custom class for generating the access token.
@@ -167,8 +168,7 @@ Doorkeeper.configure do
167
168
  # since plain values can no longer be retrieved.
168
169
  #
169
170
  # Note: If you are already a user of doorkeeper and have existing tokens
170
- # in your installation, they will be invalid without enabling the additional
171
- # setting `fallback_to_plain_secrets` below.
171
+ # in your installation, they will be invalid without adding 'fallback: :plain'.
172
172
  #
173
173
  # hash_token_secrets
174
174
  # By default, token secrets will be hashed using the
@@ -202,7 +202,9 @@ Doorkeeper.configure do
202
202
  # This will ensure that old access tokens and secrets
203
203
  # will remain valid even if the hashing above is enabled.
204
204
  #
205
- # fallback_to_plain_secrets
205
+ # This can be done by adding 'fallback: plain', e.g. :
206
+ #
207
+ # hash_application_secrets using: '::Doorkeeper::SecretStoring::BCrypt', fallback: :plain
206
208
 
207
209
  # Issue access tokens with refresh token (disabled by default), you may also
208
210
  # pass a block which accepts `context` to customize when to give a refresh
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: doorkeeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.4.0
4
+ version: 5.5.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felipe Elias Philipp
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2020-05-11 00:00:00.000000000 Z
14
+ date: 2020-08-04 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: railties
@@ -103,14 +103,14 @@ dependencies:
103
103
  requirements:
104
104
  - - "~>"
105
105
  - !ruby/object:Gem::Version
106
- version: '5.0'
106
+ version: '6.0'
107
107
  type: :development
108
108
  prerelease: false
109
109
  version_requirements: !ruby/object:Gem::Requirement
110
110
  requirements:
111
111
  - - "~>"
112
112
  - !ruby/object:Gem::Version
113
- version: '5.0'
113
+ version: '6.0'
114
114
  - !ruby/object:Gem::Dependency
115
115
  name: generator_spec
116
116
  requirement: !ruby/object:Gem::Requirement
@@ -205,8 +205,13 @@ files:
205
205
  - lib/doorkeeper/config.rb
206
206
  - lib/doorkeeper/config/abstract_builder.rb
207
207
  - lib/doorkeeper/config/option.rb
208
+ - lib/doorkeeper/config/validations.rb
208
209
  - lib/doorkeeper/engine.rb
209
210
  - lib/doorkeeper/errors.rb
211
+ - lib/doorkeeper/grant_flow.rb
212
+ - lib/doorkeeper/grant_flow/fallback_flow.rb
213
+ - lib/doorkeeper/grant_flow/flow.rb
214
+ - lib/doorkeeper/grant_flow/registry.rb
210
215
  - lib/doorkeeper/grape/authorization_decorator.rb
211
216
  - lib/doorkeeper/grape/helpers.rb
212
217
  - lib/doorkeeper/helpers/controller.rb
@@ -327,9 +332,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
327
332
  version: '2.4'
328
333
  required_rubygems_version: !ruby/object:Gem::Requirement
329
334
  requirements:
330
- - - ">="
335
+ - - ">"
331
336
  - !ruby/object:Gem::Version
332
- version: '0'
337
+ version: 1.3.1
333
338
  requirements: []
334
339
  rubygems_version: 3.0.2
335
340
  signing_key: