doorkeeper 5.3.0 → 5.4.0.rc1

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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +0 -14
  3. data/CHANGELOG.md +44 -1
  4. data/Dangerfile +7 -7
  5. data/Dockerfile +2 -2
  6. data/Gemfile +9 -9
  7. data/README.md +6 -4
  8. data/app/controllers/doorkeeper/applications_controller.rb +4 -4
  9. data/app/controllers/doorkeeper/authorizations_controller.rb +31 -12
  10. data/app/controllers/doorkeeper/authorized_applications_controller.rb +2 -2
  11. data/app/controllers/doorkeeper/tokens_controller.rb +57 -20
  12. data/app/views/doorkeeper/applications/show.html.erb +19 -2
  13. data/bin/console +14 -0
  14. data/config/locales/en.yml +3 -1
  15. data/doorkeeper.gemspec +1 -1
  16. data/gemfiles/rails_5_0.gemfile +8 -7
  17. data/gemfiles/rails_5_1.gemfile +8 -7
  18. data/gemfiles/rails_5_2.gemfile +8 -7
  19. data/gemfiles/rails_6_0.gemfile +8 -7
  20. data/gemfiles/rails_master.gemfile +8 -7
  21. data/lib/doorkeeper/config/abstract_builder.rb +28 -0
  22. data/lib/doorkeeper/config/option.rb +28 -14
  23. data/lib/doorkeeper/config.rb +41 -17
  24. data/lib/doorkeeper/grape/helpers.rb +1 -1
  25. data/lib/doorkeeper/models/access_grant_mixin.rb +9 -11
  26. data/lib/doorkeeper/models/access_token_mixin.rb +102 -42
  27. data/lib/doorkeeper/models/concerns/resource_ownerable.rb +47 -0
  28. data/lib/doorkeeper/models/concerns/revocable.rb +1 -1
  29. data/lib/doorkeeper/models/concerns/scopes.rb +5 -1
  30. data/lib/doorkeeper/models/concerns/secret_storable.rb +1 -3
  31. data/lib/doorkeeper/oauth/authorization/code.rb +14 -5
  32. data/lib/doorkeeper/oauth/authorization/context.rb +2 -2
  33. data/lib/doorkeeper/oauth/authorization/token.rb +7 -11
  34. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +4 -4
  35. data/lib/doorkeeper/oauth/authorization_code_request.rb +18 -8
  36. data/lib/doorkeeper/oauth/base_request.rb +11 -19
  37. data/lib/doorkeeper/oauth/client/credentials.rb +2 -4
  38. data/lib/doorkeeper/oauth/client.rb +1 -1
  39. data/lib/doorkeeper/oauth/client_credentials/creator.rb +25 -7
  40. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +3 -2
  41. data/lib/doorkeeper/oauth/client_credentials/validator.rb +1 -1
  42. data/lib/doorkeeper/oauth/client_credentials_request.rb +8 -7
  43. data/lib/doorkeeper/oauth/code_request.rb +1 -1
  44. data/lib/doorkeeper/oauth/code_response.rb +6 -2
  45. data/lib/doorkeeper/oauth/error_response.rb +2 -4
  46. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +1 -5
  47. data/lib/doorkeeper/oauth/hooks/context.rb +21 -0
  48. data/lib/doorkeeper/oauth/invalid_token_response.rb +2 -2
  49. data/lib/doorkeeper/oauth/password_access_token_request.rb +3 -5
  50. data/lib/doorkeeper/oauth/pre_authorization.rb +32 -27
  51. data/lib/doorkeeper/oauth/refresh_token_request.rb +18 -22
  52. data/lib/doorkeeper/oauth/token.rb +1 -1
  53. data/lib/doorkeeper/oauth/token_introspection.rb +3 -3
  54. data/lib/doorkeeper/oauth/token_request.rb +2 -2
  55. data/lib/doorkeeper/oauth/token_response.rb +1 -1
  56. data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +7 -2
  57. data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +6 -2
  58. data/lib/doorkeeper/orm/active_record/mixins/application.rb +6 -2
  59. data/lib/doorkeeper/rails/routes/abstract_router.rb +35 -0
  60. data/lib/doorkeeper/rails/routes/mapper.rb +2 -2
  61. data/lib/doorkeeper/rails/routes/registry.rb +45 -0
  62. data/lib/doorkeeper/rails/routes.rb +13 -17
  63. data/lib/doorkeeper/request/strategy.rb +2 -2
  64. data/lib/doorkeeper/server.rb +3 -3
  65. data/lib/doorkeeper/version.rb +2 -2
  66. data/lib/doorkeeper.rb +106 -79
  67. data/lib/generators/doorkeeper/confidential_applications_generator.rb +1 -1
  68. data/lib/generators/doorkeeper/enable_polymorphic_resource_owner_generator.rb +39 -0
  69. data/lib/generators/doorkeeper/templates/add_owner_to_application_migration.rb.erb +2 -0
  70. data/lib/generators/doorkeeper/templates/add_previous_refresh_token_to_access_tokens.rb.erb +2 -0
  71. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +2 -0
  72. data/lib/generators/doorkeeper/templates/enable_polymorphic_resource_owner_migration.rb.erb +17 -0
  73. data/lib/generators/doorkeeper/templates/initializer.rb +47 -3
  74. data/lib/generators/doorkeeper/templates/migration.rb.erb +2 -0
  75. data/spec/controllers/applications_controller_spec.rb +2 -2
  76. data/spec/controllers/authorizations_controller_spec.rb +165 -30
  77. data/spec/controllers/tokens_controller_spec.rb +6 -5
  78. data/spec/dummy/app/helpers/application_helper.rb +1 -1
  79. data/spec/dummy/app/models/user.rb +5 -1
  80. data/spec/dummy/config/application.rb +6 -4
  81. data/spec/dummy/config/boot.rb +4 -4
  82. data/spec/dummy/config/environment.rb +1 -1
  83. data/spec/dummy/config/routes.rb +4 -4
  84. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +2 -2
  85. data/spec/dummy/db/schema.rb +3 -1
  86. data/spec/factories.rb +1 -1
  87. data/spec/generators/enable_polymorphic_resource_owner_generator_spec.rb +47 -0
  88. data/spec/lib/config_spec.rb +30 -11
  89. data/spec/lib/models/revocable_spec.rb +2 -3
  90. data/spec/lib/models/scopes_spec.rb +8 -0
  91. data/spec/lib/oauth/authorization_code_request_spec.rb +25 -15
  92. data/spec/lib/oauth/base_request_spec.rb +6 -20
  93. data/spec/lib/oauth/client_credentials/creator_spec.rb +90 -89
  94. data/spec/lib/oauth/client_credentials/issuer_spec.rb +84 -86
  95. data/spec/lib/oauth/client_credentials/validation_spec.rb +38 -40
  96. data/spec/lib/oauth/client_credentials_request_spec.rb +5 -4
  97. data/spec/lib/oauth/code_request_spec.rb +1 -1
  98. data/spec/lib/oauth/code_response_spec.rb +5 -1
  99. data/spec/lib/oauth/error_response_spec.rb +1 -1
  100. data/spec/lib/oauth/password_access_token_request_spec.rb +24 -13
  101. data/spec/lib/oauth/pre_authorization_spec.rb +13 -18
  102. data/spec/lib/oauth/refresh_token_request_spec.rb +19 -30
  103. data/spec/lib/oauth/token_request_spec.rb +14 -7
  104. data/spec/lib/option_spec.rb +51 -0
  105. data/spec/lib/stale_records_cleaner_spec.rb +18 -5
  106. data/spec/models/doorkeeper/access_grant_spec.rb +18 -4
  107. data/spec/models/doorkeeper/access_token_spec.rb +507 -479
  108. data/spec/models/doorkeeper/application_spec.rb +338 -300
  109. data/spec/requests/endpoints/token_spec.rb +5 -1
  110. data/spec/requests/flows/authorization_code_errors_spec.rb +4 -1
  111. data/spec/requests/flows/authorization_code_spec.rb +6 -1
  112. data/spec/requests/flows/client_credentials_spec.rb +41 -0
  113. data/spec/requests/flows/refresh_token_spec.rb +16 -8
  114. data/spec/requests/flows/revoke_token_spec.rb +143 -104
  115. data/spec/support/helpers/access_token_request_helper.rb +1 -0
  116. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  117. data/spec/support/helpers/config_helper.rb +1 -1
  118. data/spec/support/shared/controllers_shared_context.rb +2 -2
  119. data/spec/support/shared/models_shared_examples.rb +6 -4
  120. metadata +15 -4
@@ -3,13 +3,14 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", git: "https://github.com/rails/rails"
6
- gem "rspec-core", git: "https://github.com/rspec/rspec-core.git"
7
- gem "rspec-expectations", git: "https://github.com/rspec/rspec-expectations.git"
8
- gem "rspec-mocks", git: "https://github.com/rspec/rspec-mocks.git"
9
- gem "rspec-rails", "4.0.0.beta3"
10
- gem "rspec-support", git: "https://github.com/rspec/rspec-support.git"
11
- gem "rubocop", "~> 0.75"
12
- gem "rubocop-performance"
6
+ gem "rspec-core"
7
+ gem "rspec-expectations"
8
+ gem "rspec-mocks"
9
+ gem "rspec-rails", "~> 4.0"
10
+ gem "rspec-support"
11
+ gem "rubocop", "~> 0.80"
12
+ gem "rubocop-performance", require: false
13
+ gem "rubocop-rails", require: false
13
14
  gem "bcrypt", "~> 3.1", require: false
14
15
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
15
16
  gem "sqlite3", "~> 1.4", platform: [:ruby, :mswin, :mingw, :x64_mingw]
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ class Config
5
+ # Abstract base class for Doorkeeper and it's extensions configuration
6
+ # builder. Instantiates and validates gem configuration.
7
+ #
8
+ class AbstractBuilder
9
+ attr_reader :config
10
+
11
+ # @param [Class] config class
12
+ #
13
+ def initialize(config = Config.new, &block)
14
+ @config = config
15
+ instance_eval(&block)
16
+ end
17
+
18
+ # Builds and validates configuration.
19
+ #
20
+ # @return [Doorkeeper::Config] config instance
21
+ #
22
+ def build
23
+ @config.validate if @config.respond_to?(:validate)
24
+ @config
25
+ end
26
+ end
27
+ end
28
+ end
@@ -36,22 +36,29 @@ module Doorkeeper
36
36
  attribute = options[:as] || name
37
37
  attribute_builder = options[:builder_class]
38
38
 
39
- Builder.instance_eval do
40
- remove_method name if method_defined?(name)
41
- if options[:deprecated]
42
- define_method name do |*_, &_|
43
- Kernel.warn "[DOORKEEPER] #{name} has been deprecated and will soon be removed"
44
- end
45
- else
46
- define_method name do |*args, &block|
47
- value = if attribute_builder
48
- attribute_builder.new(&block).build
49
- else
50
- block || args.first
51
- end
39
+ builder_class.instance_eval do
40
+ if method_defined?(name)
41
+ Kernel.warn "[DOORKEEPER] Option #{name} already defined and will be overridden"
42
+ remove_method name
43
+ end
52
44
 
53
- @config.instance_variable_set(:"@#{attribute}", value)
45
+ define_method name do |*args, &block|
46
+ if (deprecation_opts = options[:deprecated])
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
51
+
52
+ Kernel.warn(warning)
54
53
  end
54
+
55
+ value = if attribute_builder
56
+ attribute_builder.new(&block).build
57
+ else
58
+ block || args.first
59
+ end
60
+
61
+ @config.instance_variable_set(:"@#{attribute}", value)
55
62
  end
56
63
  end
57
64
 
@@ -65,6 +72,13 @@ module Doorkeeper
65
72
 
66
73
  public attribute
67
74
  end
75
+
76
+ def self.extended(base)
77
+ return if base.respond_to?(:builder_class)
78
+
79
+ raise Doorkeeper::MissingConfigurationBuilderClass, "Define `self.builder_class` method " \
80
+ "for #{base} that returns your custom Builder class to use options DSL!"
81
+ end
68
82
  end
69
83
  end
70
84
  end
@@ -1,16 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "doorkeeper/config/option"
4
+ require "doorkeeper/config/abstract_builder"
4
5
 
5
6
  module Doorkeeper
7
+ # Defines a MissingConfiguration error for a missing Doorkeeper configuration
8
+ #
6
9
  class MissingConfiguration < StandardError
7
- # Defines a MissingConfiguration error for a missing Doorkeeper
8
- # configuration
9
10
  def initialize
10
11
  super("Configuration for doorkeeper missing. Do you have doorkeeper initializer?")
11
12
  end
12
13
  end
13
14
 
15
+ # Doorkeeper option DSL could be reused in extensions to build their own
16
+ # configurations. To use the Option DSL gems need to define `builder_class` method
17
+ # that returns configuration Builder class. This exception raises when they don't
18
+ # define it.
19
+ #
20
+ class MissingConfigurationBuilderClass < StandardError; end
21
+
14
22
  class << self
15
23
  def configure(&block)
16
24
  @config = Config::Builder.new(&block).build
@@ -30,7 +38,7 @@ module Doorkeeper
30
38
  @orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize
31
39
  rescue NameError => e
32
40
  raise e, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.strip_heredoc
33
- [doorkeeper] ORM adapter not found (#{configuration.orm}), or there was an error
41
+ [DOORKEEPER] ORM adapter not found (#{configuration.orm}), or there was an error
34
42
  trying to load it.
35
43
 
36
44
  You probably need to add the related gem for this adapter to work with
@@ -48,17 +56,8 @@ module Doorkeeper
48
56
  end
49
57
 
50
58
  class Config
51
- class Builder
52
- def initialize(&block)
53
- @config = Config.new
54
- instance_eval(&block)
55
- end
56
-
57
- def build
58
- @config.validate
59
- @config
60
- end
61
-
59
+ # Default Doorkeeper configuration builder
60
+ class Builder < AbstractBuilder
62
61
  # Provide support for an owner to be assigned to each registered
63
62
  # application (disabled by default)
64
63
  # Optional parameter confirmation: true (default false) if you want
@@ -158,6 +157,12 @@ module Doorkeeper
158
157
  @config.instance_variable_set(:@api_only, true)
159
158
  end
160
159
 
160
+ # Enables polymorphic Resource Owner association for Access Grant and
161
+ # Access Token models. Requires additional database columns to be setup.
162
+ def use_polymorphic_resource_owner
163
+ @config.instance_variable_set(:@polymorphic_resource_owner, true)
164
+ end
165
+
161
166
  # Forbids creating/updating applications with arbitrary scopes that are
162
167
  # not in configuration, i.e. `default_scopes` or `optional_scopes`.
163
168
  # (disabled by default)
@@ -219,6 +224,9 @@ module Doorkeeper
219
224
  end
220
225
  end
221
226
 
227
+ # Replace with `default: Builder` when we drop support of Rails < 5.2
228
+ mattr_reader(:builder_class) { Builder }
229
+
222
230
  extend Option
223
231
 
224
232
  option :resource_owner_authenticator,
@@ -251,8 +259,8 @@ module Doorkeeper
251
259
  end)
252
260
 
253
261
  # Hooks for authorization
254
- option :before_successful_authorization, default: ->(_context) {}
255
- option :after_successful_authorization, default: ->(_context) {}
262
+ option :before_successful_authorization, default: ->(_controller, _context = nil) {}
263
+ option :after_successful_authorization, default: ->(_controller, _context = nil) {}
256
264
  # Hooks for strategies responses
257
265
  option :before_successful_strategy_response, default: ->(_request) {}
258
266
  option :after_successful_strategy_response, default: ->(_request, _response) {}
@@ -265,9 +273,17 @@ module Doorkeeper
265
273
  option :authorization_code_expires_in, default: 600
266
274
  option :orm, default: :active_record
267
275
  option :native_redirect_uri, default: "urn:ietf:wg:oauth:2.0:oob", deprecated: true
268
- option :active_record_options, default: {}
269
276
  option :grant_flows, default: %w[authorization_code client_credentials]
270
277
  option :handle_auth_errors, default: :render
278
+ option :token_lookup_batch_size, default: 10_000
279
+
280
+ option :active_record_options,
281
+ default: {},
282
+ deprecated: { message: "Customize Doorkeeper models instead" }
283
+
284
+ # Hook to allow arbitrary user-client authorization
285
+ option :authorize_resource_owner_for_client,
286
+ default: ->(_client, _resource_owner) { true }
271
287
 
272
288
  # Allows to customize OAuth grant flows that +each+ application support.
273
289
  # You can configure a custom block (or use a class respond to `#call`) that must
@@ -471,6 +487,10 @@ module Doorkeeper
471
487
  option_set? :enable_application_owner
472
488
  end
473
489
 
490
+ def polymorphic_resource_owner?
491
+ option_set? :polymorphic_resource_owner
492
+ end
493
+
474
494
  def confirm_application_owner?
475
495
  option_set? :confirm_application_owner
476
496
  end
@@ -479,6 +499,10 @@ module Doorkeeper
479
499
  handle_auth_errors == :raise
480
500
  end
481
501
 
502
+ def application_secret_hashed?
503
+ instance_variable_defined?(:"@application_secret_strategy")
504
+ end
505
+
482
506
  def token_secret_strategy
483
507
  @token_secret_strategy ||= ::Doorkeeper::SecretStoring::Plain
484
508
  end
@@ -18,7 +18,7 @@ module Doorkeeper
18
18
 
19
19
  scopes = if endpoint_scopes
20
20
  Doorkeeper::OAuth::Scopes.from_array(endpoint_scopes)
21
- elsif scopes && !scopes.empty?
21
+ elsif scopes.present?
22
22
  Doorkeeper::OAuth::Scopes.from_array(scopes)
23
23
  end
24
24
 
@@ -11,14 +11,11 @@ module Doorkeeper
11
11
  include Models::Orderable
12
12
  include Models::SecretStorable
13
13
  include Models::Scopes
14
+ include Models::ResourceOwnerable
14
15
 
15
16
  # never uses pkce, if pkce migrations were not generated
16
17
  def uses_pkce?
17
- pkce_supported? && code_challenge.present?
18
- end
19
-
20
- def pkce_supported?
21
- respond_to? :code_challenge
18
+ self.class.pkce_supported? && code_challenge.present?
22
19
  end
23
20
 
24
21
  module ClassMethods
@@ -43,11 +40,12 @@ module Doorkeeper
43
40
  # instance of the Resource Owner model
44
41
  #
45
42
  def revoke_all_for(application_id, resource_owner, clock = Time)
46
- where(
47
- application_id: application_id,
48
- resource_owner_id: resource_owner.id,
49
- revoked_at: nil,
50
- ).update_all(revoked_at: clock.now.utc)
43
+ by_resource_owner(resource_owner)
44
+ .where(
45
+ application_id: application_id,
46
+ revoked_at: nil,
47
+ )
48
+ .update_all(revoked_at: clock.now.utc)
51
49
  end
52
50
 
53
51
  # Implements PKCE code_challenge encoding without base64 padding as described in the spec.
@@ -96,7 +94,7 @@ module Doorkeeper
96
94
  end
97
95
 
98
96
  def pkce_supported?
99
- new.pkce_supported?
97
+ column_names.include?("code_challenge")
100
98
  end
101
99
 
102
100
  ##
@@ -12,6 +12,7 @@ module Doorkeeper
12
12
  include Models::Orderable
13
13
  include Models::SecretStorable
14
14
  include Models::Scopes
15
+ include Models::ResourceOwnerable
15
16
 
16
17
  module ClassMethods
17
18
  # Returns an instance of the Doorkeeper::AccessToken with
@@ -64,11 +65,12 @@ module Doorkeeper
64
65
  # instance of the Resource Owner model
65
66
  #
66
67
  def revoke_all_for(application_id, resource_owner, clock = Time)
67
- where(
68
- application_id: application_id,
69
- resource_owner_id: resource_owner.id,
70
- revoked_at: nil,
71
- ).update_all(revoked_at: clock.now.utc)
68
+ by_resource_owner(resource_owner)
69
+ .where(
70
+ application_id: application_id,
71
+ revoked_at: nil,
72
+ )
73
+ .update_all(revoked_at: clock.now.utc)
72
74
  end
73
75
 
74
76
  # Looking for not revoked Access Token with a matching set of scopes
@@ -76,7 +78,7 @@ module Doorkeeper
76
78
  #
77
79
  # @param application [Doorkeeper::Application]
78
80
  # Application instance
79
- # @param resource_owner_or_id [ActiveRecord::Base, Integer]
81
+ # @param resource_owner [ActiveRecord::Base, Integer]
80
82
  # Resource Owner model instance or it's ID
81
83
  # @param scopes [String, Doorkeeper::OAuth::Scopes]
82
84
  # set of scopes
@@ -84,14 +86,8 @@ module Doorkeeper
84
86
  # @return [Doorkeeper::AccessToken, nil] Access Token instance or
85
87
  # nil if matching record was not found
86
88
  #
87
- def matching_token_for(application, resource_owner_or_id, scopes)
88
- resource_owner_id = if resource_owner_or_id.respond_to?(:to_key)
89
- resource_owner_or_id.id
90
- else
91
- resource_owner_or_id
92
- end
93
-
94
- tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
89
+ def matching_token_for(application, resource_owner, scopes)
90
+ tokens = authorized_tokens_for(application&.id, resource_owner)
95
91
  find_matching_token(tokens, application, scopes)
96
92
  end
97
93
 
@@ -126,10 +122,11 @@ module Doorkeeper
126
122
  return nil unless relation
127
123
 
128
124
  matching_tokens = []
125
+ batch_size = Doorkeeper.configuration.token_lookup_batch_size
129
126
 
130
- find_access_token_in_batches(relation) do |batch|
127
+ find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
131
128
  tokens = batch.select do |token|
132
- scopes_match?(token.scopes, scopes, application.try(:scopes))
129
+ scopes_match?(token.scopes, scopes, application&.scopes)
133
130
  end
134
131
 
135
132
  matching_tokens.concat(tokens)
@@ -169,47 +166,78 @@ module Doorkeeper
169
166
  #
170
167
  # @param application [Doorkeeper::Application]
171
168
  # Application instance
172
- # @param resource_owner_id [ActiveRecord::Base, Integer]
169
+ # @param resource_owner [ActiveRecord::Base, Integer]
173
170
  # Resource Owner model instance or it's ID
174
171
  # @param scopes [#to_s]
175
172
  # set of scopes (any object that responds to `#to_s`)
176
- # @param expires_in [Integer]
173
+ # @param token_attributes [Hash]
174
+ # Additional attributes to use when creating a token
175
+ # @option token_attributes [Integer] :expires_in
177
176
  # token lifetime in seconds
178
- # @param use_refresh_token [Boolean]
177
+ # @option token_attributes [Boolean] :use_refresh_token
179
178
  # whether to use the refresh token
180
179
  #
181
180
  # @return [Doorkeeper::AccessToken] existing record or a new one
182
181
  #
183
- def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
182
+ def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
184
183
  if Doorkeeper.config.reuse_access_token
185
- access_token = matching_token_for(application, resource_owner_id, scopes)
184
+ access_token = matching_token_for(application, resource_owner, scopes)
186
185
 
187
186
  return access_token if access_token&.reusable?
188
187
  end
189
188
 
190
- create!(
191
- application_id: application.try(:id),
192
- resource_owner_id: resource_owner_id,
193
- scopes: scopes.to_s,
194
- expires_in: expires_in,
195
- use_refresh_token: use_refresh_token,
189
+ create_for(
190
+ application: application,
191
+ resource_owner: resource_owner,
192
+ scopes: scopes,
193
+ **token_attributes,
196
194
  )
197
195
  end
198
196
 
197
+ # Creates a not expired AccessToken record with a matching set of
198
+ # scopes that belongs to specific Application and Resource Owner.
199
+ #
200
+ # @param application [Doorkeeper::Application]
201
+ # Application instance
202
+ # @param resource_owner [ActiveRecord::Base, Integer]
203
+ # Resource Owner model instance or it's ID
204
+ # @param scopes [#to_s]
205
+ # set of scopes (any object that responds to `#to_s`)
206
+ # @param token_attributes [Hash]
207
+ # Additional attributes to use when creating a token
208
+ # @option token_attributes [Integer] :expires_in
209
+ # token lifetime in seconds
210
+ # @option token_attributes [Boolean] :use_refresh_token
211
+ # whether to use the refresh token
212
+ #
213
+ # @return [Doorkeeper::AccessToken] new access token
214
+ #
215
+ def create_for(application:, resource_owner:, scopes:, **token_attributes)
216
+ token_attributes[:application_id] = application&.id
217
+ token_attributes[:scopes] = scopes.to_s
218
+
219
+ if Doorkeeper.config.polymorphic_resource_owner?
220
+ token_attributes[:resource_owner] = resource_owner
221
+ else
222
+ token_attributes[:resource_owner_id] = resource_owner_id_for(resource_owner)
223
+ end
224
+
225
+ create!(token_attributes)
226
+ end
227
+
199
228
  # Looking for not revoked Access Token records that belongs to specific
200
229
  # Application and Resource Owner.
201
230
  #
202
231
  # @param application_id [Integer]
203
232
  # ID of the Application model instance
204
- # @param resource_owner_id [Integer]
233
+ # @param resource_owner [Integer]
205
234
  # ID of the Resource Owner model instance
206
235
  #
207
236
  # @return [Doorkeeper::AccessToken] array of matching AccessToken objects
208
237
  #
209
- def authorized_tokens_for(application_id, resource_owner_id)
210
- where(
238
+ def authorized_tokens_for(application_id, resource_owner)
239
+ by_resource_owner(resource_owner).where(
211
240
  application_id: application_id,
212
- resource_owner_id: resource_owner_id,
213
241
  revoked_at: nil,
214
242
  )
215
243
  end
@@ -219,15 +247,16 @@ module Doorkeeper
219
247
  #
220
248
  # @param application_id [Integer]
221
249
  # ID of the Application model instance
222
- # @param resource_owner_id [Integer]
250
+ # @param resource_owner [ActiveRecord::Base, Integer]
223
251
  # ID of the Resource Owner model instance
224
252
  #
225
253
  # @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
226
254
  # nil if nothing was found
227
255
  #
228
- def last_authorized_token_for(application_id, resource_owner_id)
229
- authorized_tokens_for(application_id, resource_owner_id)
230
- .ordered_by(:created_at, :desc).first
256
+ def last_authorized_token_for(application_id, resource_owner)
257
+ authorized_tokens_for(application_id, resource_owner)
258
+ .ordered_by(:created_at, :desc)
259
+ .first
231
260
  end
232
261
 
233
262
  ##
@@ -268,7 +297,11 @@ module Doorkeeper
268
297
  expires_in: expires_in_seconds,
269
298
  application: { uid: application.try(:uid) },
270
299
  created_at: created_at.to_i,
271
- }
300
+ }.tap do |json|
301
+ if Doorkeeper.configuration.polymorphic_resource_owner?
302
+ json[:resource_owner_type] = resource_owner_type
303
+ end
304
+ end
272
305
  end
273
306
 
274
307
  # Indicates whether the token instance have the same credential
@@ -280,7 +313,22 @@ module Doorkeeper
280
313
  #
281
314
  def same_credential?(access_token)
282
315
  application_id == access_token.application_id &&
316
+ same_resource_owner?(access_token)
317
+ end
318
+
319
+ # Indicates whether the token instance have the same credential
320
+ # as the other Access Token.
321
+ #
322
+ # @param access_token [Doorkeeper::AccessToken] other token
323
+ #
324
+ # @return [Boolean] true if credentials are same of false in other cases
325
+ #
326
+ def same_resource_owner?(access_token)
327
+ if Doorkeeper.configuration.polymorphic_resource_owner?
328
+ resource_owner == access_token.resource_owner
329
+ else
283
330
  resource_owner_id == access_token.resource_owner_id
331
+ end
284
332
  end
285
333
 
286
334
  # Indicates if token is acceptable for specific scopes.
@@ -325,7 +373,7 @@ module Doorkeeper
325
373
  return unless self.class.refresh_token_revoked_on_use?
326
374
 
327
375
  old_refresh_token&.revoke
328
- update_attribute :previous_refresh_token, ""
376
+ update_column(:previous_refresh_token, "")
329
377
  end
330
378
 
331
379
  private
@@ -362,16 +410,28 @@ module Doorkeeper
362
410
  def generate_token
363
411
  self.created_at ||= Time.now.utc
364
412
 
365
- @raw_token = token_generator.generate(
413
+ @raw_token = token_generator.generate(attributes_for_token_generator)
414
+ secret_strategy.store_secret(self, :token, @raw_token)
415
+ @raw_token
416
+ end
417
+
418
+ # Set of attributes that would be passed to token generator to
419
+ # generate unique token based on them.
420
+ #
421
+ # @return [Hash] set of attributes
422
+ #
423
+ def attributes_for_token_generator
424
+ {
366
425
  resource_owner_id: resource_owner_id,
367
426
  scopes: scopes,
368
427
  application: application,
369
428
  expires_in: expires_in,
370
429
  created_at: created_at,
371
- )
372
-
373
- secret_strategy.store_secret(self, :token, @raw_token)
374
- @raw_token
430
+ }.tap do |attributes|
431
+ if Doorkeeper.config.polymorphic_resource_owner?
432
+ attributes[:resource_owner] = resource_owner
433
+ end
434
+ end
375
435
  end
376
436
 
377
437
  def token_generator
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module Models
5
+ module ResourceOwnerable
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Searches for record by Resource Owner considering Doorkeeper
10
+ # configuration for resource owner association.
11
+ #
12
+ # @param resource_owner [ActiveRecord::Base, Integer]
13
+ # resource owner
14
+ #
15
+ # @return [Doorkeeper::AccessGrant, Doorkeeper::AccessToken]
16
+ # collection of records
17
+ #
18
+ def by_resource_owner(resource_owner)
19
+ if Doorkeeper.configuration.polymorphic_resource_owner?
20
+ where(resource_owner: resource_owner)
21
+ else
22
+ where(resource_owner_id: resource_owner_id_for(resource_owner))
23
+ end
24
+ end
25
+
26
+ protected
27
+
28
+ # Backward compatible way to retrieve resource owner itself (if
29
+ # polymorphic association enabled) or just it's ID.
30
+ #
31
+ # @param resource_owner [ActiveRecord::Base, Integer]
32
+ # resource owner
33
+ #
34
+ # @return [ActiveRecord::Base, Integer]
35
+ # instance of Resource Owner or it's ID
36
+ #
37
+ def resource_owner_id_for(resource_owner)
38
+ if resource_owner.respond_to?(:to_key)
39
+ resource_owner.id
40
+ else
41
+ resource_owner
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -9,7 +9,7 @@ module Doorkeeper
9
9
  # @param clock [Time] time object
10
10
  #
11
11
  def revoke(clock = Time)
12
- update_attribute :revoked_at, clock.now.utc
12
+ update_column(:revoked_at, clock.now.utc)
13
13
  end
14
14
 
15
15
  # Indicates whether the object has been revoked.
@@ -8,7 +8,11 @@ module Doorkeeper
8
8
  end
9
9
 
10
10
  def scopes=(value)
11
- super Array(value).join(" ")
11
+ if value.is_a?(Array)
12
+ super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
13
+ else
14
+ super(Doorkeeper::OAuth::Scopes.from_string(value.to_s).to_s)
15
+ end
12
16
  end
13
17
 
14
18
  def scopes_string
@@ -25,9 +25,7 @@ module Doorkeeper
25
25
  # @return [Boolean]
26
26
  # Whether input matches secret as per the secret strategy
27
27
  #
28
- def secret_matches?(input, secret)
29
- secret_strategy.secret_matches?(input, secret)
30
- end
28
+ delegate :secret_matches?, to: :secret_strategy
31
29
 
32
30
  # Returns an instance of the Doorkeeper::AccessToken with
33
31
  # specific token value.
@@ -4,7 +4,7 @@ module Doorkeeper
4
4
  module OAuth
5
5
  module Authorization
6
6
  class Code
7
- attr_accessor :pre_auth, :resource_owner, :token
7
+ attr_reader :pre_auth, :resource_owner, :token
8
8
 
9
9
  def initialize(pre_auth, resource_owner)
10
10
  @pre_auth = pre_auth
@@ -12,7 +12,9 @@ module Doorkeeper
12
12
  end
13
13
 
14
14
  def issue_token
15
- @token ||= Doorkeeper.config.access_grant_model.create!(access_grant_attributes)
15
+ return @token if defined?(@token)
16
+
17
+ @token = Doorkeeper.config.access_grant_model.create!(access_grant_attributes)
16
18
  end
17
19
 
18
20
  def oob_redirect
@@ -26,13 +28,20 @@ module Doorkeeper
26
28
  end
27
29
 
28
30
  def access_grant_attributes
29
- pkce_attributes.merge(
31
+ attributes = {
30
32
  application_id: pre_auth.client.id,
31
- resource_owner_id: resource_owner.id,
32
33
  expires_in: authorization_code_expires_in,
33
34
  redirect_uri: pre_auth.redirect_uri,
34
35
  scopes: pre_auth.scopes.to_s,
35
- )
36
+ }
37
+
38
+ if Doorkeeper.config.polymorphic_resource_owner?
39
+ attributes[:resource_owner] = resource_owner
40
+ else
41
+ attributes[:resource_owner_id] = resource_owner.id
42
+ end
43
+
44
+ pkce_attributes.merge(attributes)
36
45
  end
37
46
 
38
47
  def pkce_attributes