doorkeeper 5.4.0.rc2 → 5.5.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -10
  3. data/README.md +4 -4
  4. data/app/controllers/doorkeeper/application_controller.rb +1 -0
  5. data/app/controllers/doorkeeper/authorizations_controller.rb +16 -5
  6. data/app/controllers/doorkeeper/authorized_applications_controller.rb +1 -1
  7. data/app/controllers/doorkeeper/token_info_controller.rb +12 -2
  8. data/app/controllers/doorkeeper/tokens_controller.rb +34 -26
  9. data/app/views/doorkeeper/applications/show.html.erb +16 -12
  10. data/app/views/doorkeeper/authorizations/form_post.html.erb +11 -0
  11. data/config/locales/en.yml +3 -1
  12. data/lib/doorkeeper.rb +5 -0
  13. data/lib/doorkeeper/config.rb +91 -62
  14. data/lib/doorkeeper/config/option.rb +1 -3
  15. data/lib/doorkeeper/config/validations.rb +53 -0
  16. data/lib/doorkeeper/engine.rb +1 -1
  17. data/lib/doorkeeper/grant_flow.rb +45 -0
  18. data/lib/doorkeeper/grant_flow/fallback_flow.rb +15 -0
  19. data/lib/doorkeeper/grant_flow/flow.rb +44 -0
  20. data/lib/doorkeeper/grant_flow/registry.rb +50 -0
  21. data/lib/doorkeeper/helpers/controller.rb +4 -0
  22. data/lib/doorkeeper/models/access_grant_mixin.rb +1 -2
  23. data/lib/doorkeeper/models/access_token_mixin.rb +4 -4
  24. data/lib/doorkeeper/models/concerns/revocable.rb +1 -1
  25. data/lib/doorkeeper/oauth/authorization/code.rb +5 -1
  26. data/lib/doorkeeper/oauth/authorization/context.rb +5 -5
  27. data/lib/doorkeeper/oauth/authorization/token.rb +11 -5
  28. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +1 -1
  29. data/lib/doorkeeper/oauth/authorization_code_request.rb +10 -17
  30. data/lib/doorkeeper/oauth/base_request.rb +1 -1
  31. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  32. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +1 -0
  33. data/lib/doorkeeper/oauth/code_request.rb +2 -2
  34. data/lib/doorkeeper/oauth/code_response.rb +17 -11
  35. data/lib/doorkeeper/oauth/error_response.rb +4 -3
  36. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -3
  37. data/lib/doorkeeper/oauth/password_access_token_request.rb +23 -3
  38. data/lib/doorkeeper/oauth/pre_authorization.rb +33 -8
  39. data/lib/doorkeeper/oauth/refresh_token_request.rb +13 -0
  40. data/lib/doorkeeper/oauth/token.rb +3 -3
  41. data/lib/doorkeeper/oauth/token_introspection.rb +1 -5
  42. data/lib/doorkeeper/oauth/token_request.rb +1 -1
  43. data/lib/doorkeeper/orm/active_record.rb +5 -14
  44. data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +11 -1
  45. data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +9 -1
  46. data/lib/doorkeeper/orm/active_record/mixins/application.rb +26 -15
  47. data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +5 -0
  48. data/lib/doorkeeper/rails/routes.rb +1 -3
  49. data/lib/doorkeeper/rake/db.rake +3 -3
  50. data/lib/doorkeeper/rake/setup.rake +5 -0
  51. data/lib/doorkeeper/request.rb +49 -12
  52. data/lib/doorkeeper/request/password.rb +1 -0
  53. data/lib/doorkeeper/server.rb +1 -1
  54. data/lib/doorkeeper/stale_records_cleaner.rb +4 -4
  55. data/lib/doorkeeper/version.rb +3 -7
  56. data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb +1 -1
  57. data/lib/generators/doorkeeper/templates/initializer.rb +9 -7
  58. metadata +26 -13
@@ -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
@@ -28,6 +29,8 @@ module Doorkeeper
28
29
  @config
29
30
  end
30
31
 
32
+ # @return [Doorkeeper::Config] configuration instance
33
+ #
31
34
  def configuration
32
35
  @config || (raise MissingConfiguration)
33
36
  end
@@ -219,6 +222,7 @@ module Doorkeeper
219
222
  mattr_reader(:builder_class) { Builder }
220
223
 
221
224
  extend Option
225
+ include Validations
222
226
 
223
227
  option :resource_owner_authenticator,
224
228
  as: :authenticate_resource_owner,
@@ -267,7 +271,6 @@ module Doorkeeper
267
271
  option :grant_flows, default: %w[authorization_code client_credentials]
268
272
  option :handle_auth_errors, default: :render
269
273
  option :token_lookup_batch_size, default: 10_000
270
-
271
274
  # Sets the token_reuse_limit
272
275
  # It will be used only when reuse_access_token option in enabled
273
276
  # By default it will be 100
@@ -275,6 +278,21 @@ module Doorkeeper
275
278
  # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1189
276
279
  option :token_reuse_limit, default: 100
277
280
 
281
+ # Don't require client authentication for password grants. If client credentials
282
+ # are present they will still be validated, and the grant rejected if the credentials
283
+ # are invalid.
284
+ #
285
+ # This is discouraged. Spec says that password grants always require a client.
286
+ #
287
+ # See https://github.com/doorkeeper-gem/doorkeeper/issues/1412#issuecomment-632750422
288
+ # and https://github.com/doorkeeper-gem/doorkeeper/pull/1420
289
+ #
290
+ # Since many applications use this unsafe behavior in the wild, this is kept as a
291
+ # not-recommended option. You should be aware that you are not following the OAuth
292
+ # spec, and understand the security implications of doing so.
293
+ option :skip_client_authentication_for_password_grant,
294
+ default: false
295
+
278
296
  option :active_record_options,
279
297
  default: {},
280
298
  deprecated: { message: "Customize Doorkeeper models instead" }
@@ -423,13 +441,6 @@ module Doorkeeper
423
441
  :token_secret_fallback_strategy,
424
442
  :application_secret_fallback_strategy
425
443
 
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
444
  # Doorkeeper Access Token model class.
434
445
  #
435
446
  # @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model]
@@ -545,12 +556,77 @@ module Doorkeeper
545
556
  ]
546
557
  end
547
558
 
559
+ def enabled_grant_flows
560
+ @enabled_grant_flows ||= calculate_grant_flows.map { |name| Doorkeeper::GrantFlow.get(name) }.compact
561
+ end
562
+
563
+ def authorization_response_flows
564
+ @authorization_response_flows ||= enabled_grant_flows.select(&:handles_response_type?) +
565
+ deprecated_authorization_flows
566
+ end
567
+
568
+ def token_grant_flows
569
+ @token_grant_flows ||= calculate_token_grant_flows
570
+ end
571
+
548
572
  def authorization_response_types
549
- @authorization_response_types ||= calculate_authorization_response_types.freeze
573
+ authorization_response_flows.map(&:response_type_matches)
550
574
  end
551
575
 
552
576
  def token_grant_types
553
- @token_grant_types ||= calculate_token_grant_types.freeze
577
+ token_grant_flows.map(&:grant_type_matches)
578
+ end
579
+
580
+ # [NOTE]: deprecated and will be removed soon
581
+ def deprecated_token_grant_types_resolver
582
+ @deprecated_token_grant_types ||= calculate_token_grant_types
583
+ end
584
+
585
+ # [NOTE]: deprecated and will be removed soon
586
+ def deprecated_authorization_flows
587
+ response_types = calculate_authorization_response_types
588
+
589
+ if response_types.any?
590
+ ::Kernel.warn <<~WARNING
591
+ Please, don't patch Doorkeeper::Config#calculate_authorization_response_types method.
592
+ Register your custom grant flows using the public API:
593
+ `Doorkeeper::GrantFlow.register(grant_flow_name, **options)`.
594
+ WARNING
595
+ end
596
+
597
+ response_types.map do |response_type|
598
+ Doorkeeper::GrantFlow::FallbackFlow.new(response_type, response_type_matches: response_type)
599
+ end
600
+ end
601
+
602
+ # [NOTE]: deprecated and will be removed soon
603
+ def calculate_authorization_response_types
604
+ []
605
+ end
606
+
607
+ # [NOTE]: deprecated and will be removed soon
608
+ def calculate_token_grant_types
609
+ types = grant_flows - ["implicit"]
610
+ types << "refresh_token" if refresh_token_enabled?
611
+ types
612
+ end
613
+
614
+ # Calculates grant flows configured by the user in Doorkeeper
615
+ # configuration considering registered aliases that is exposed
616
+ # to single or multiple other flows.
617
+ #
618
+ def calculate_grant_flows
619
+ configured_flows = grant_flows.map(&:to_s)
620
+ aliases = Doorkeeper::GrantFlow.aliases.keys.map(&:to_s)
621
+
622
+ flows = configured_flows - aliases
623
+ aliases.each do |flow_alias|
624
+ next unless configured_flows.include?(flow_alias)
625
+
626
+ flows.concat(Doorkeeper::GrantFlow.expand_alias(flow_alias))
627
+ end
628
+
629
+ flows.flatten.uniq
554
630
  end
555
631
 
556
632
  def allow_blank_redirect_uri?(application = nil)
@@ -579,57 +655,10 @@ module Doorkeeper
579
655
  !!(defined?(var) && var)
580
656
  end
581
657
 
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
658
+ def calculate_token_grant_flows
659
+ flows = enabled_grant_flows.select(&:handles_grant_type?)
660
+ flows << Doorkeeper::GrantFlow.get("refresh_token") if refresh_token_enabled?
661
+ flows
633
662
  end
634
663
  end
635
664
  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
@@ -4,7 +4,7 @@ module Doorkeeper
4
4
  class Engine < Rails::Engine
5
5
  initializer "doorkeeper.params.filter" do |app|
6
6
  parameters = %w[client_secret code authentication_token access_token refresh_token]
7
- app.config.filter_parameters << /^(#{Regexp.union parameters})$/
7
+ app.config.filter_parameters << /^(#{Regexp.union(parameters)})$/
8
8
  end
9
9
 
10
10
  initializer "doorkeeper.routes" do
@@ -0,0 +1,45 @@
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_mode_matches: %w[fragment form_post],
15
+ response_type_strategy: Doorkeeper::Request::Token,
16
+ )
17
+
18
+ register(
19
+ :authorization_code,
20
+ response_type_matches: "code",
21
+ response_mode_matches: %w[query fragment form_post],
22
+ response_type_strategy: Doorkeeper::Request::Code,
23
+ grant_type_matches: "authorization_code",
24
+ grant_type_strategy: Doorkeeper::Request::AuthorizationCode,
25
+ )
26
+
27
+ register(
28
+ :client_credentials,
29
+ grant_type_matches: "client_credentials",
30
+ grant_type_strategy: Doorkeeper::Request::ClientCredentials,
31
+ )
32
+
33
+ register(
34
+ :password,
35
+ grant_type_matches: "password",
36
+ grant_type_strategy: Doorkeeper::Request::Password,
37
+ )
38
+
39
+ register(
40
+ :refresh_token,
41
+ grant_type_matches: "refresh_token",
42
+ grant_type_strategy: Doorkeeper::Request::RefreshToken,
43
+ )
44
+ end
45
+ 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,44 @@
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
+ :response_mode_matches
9
+
10
+ def initialize(name, **options)
11
+ @name = name
12
+ @grant_type_matches = options[:grant_type_matches]
13
+ @grant_type_strategy = options[:grant_type_strategy]
14
+ @response_type_matches = options[:response_type_matches]
15
+ @response_type_strategy = options[:response_type_strategy]
16
+ @response_mode_matches = options[:response_mode_matches]
17
+ end
18
+
19
+ def handles_grant_type?
20
+ grant_type_matches.present?
21
+ end
22
+
23
+ def handles_response_type?
24
+ response_type_matches.present?
25
+ end
26
+
27
+ def matches_grant_type?(value)
28
+ grant_type_matches === value
29
+ end
30
+
31
+ def matches_response_type?(value)
32
+ response_type_matches === value
33
+ end
34
+
35
+ def default_response_mode
36
+ response_mode_matches[0]
37
+ end
38
+
39
+ def matches_response_mode?(value)
40
+ response_mode_matches.include?(value)
41
+ end
42
+ end
43
+ end
44
+ 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
@@ -36,6 +38,8 @@ module Doorkeeper
36
38
 
37
39
  # :doc:
38
40
  def doorkeeper_token
41
+ return @doorkeeper_token if defined?(@doorkeeper_token)
42
+
39
43
  @doorkeeper_token ||= OAuth::Token.authenticate(request, *config_methods)
40
44
  end
41
45