doorkeeper-sequel 1.5.0 → 2.4.0

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 (50) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +6 -0
  3. data/.gitignore +1 -0
  4. data/.gitmodules +0 -1
  5. data/.rubocop.yml +29 -2
  6. data/.travis.yml +3 -12
  7. data/CHANGELOG.md +13 -3
  8. data/Gemfile +13 -9
  9. data/README.md +18 -3
  10. data/Rakefile +22 -18
  11. data/config/locales/en.yml +11 -0
  12. data/doorkeeper-sequel.gemspec +27 -23
  13. data/gemfiles/rails-5.0.gemfile +9 -6
  14. data/gemfiles/rails-5.1.gemfile +9 -6
  15. data/gemfiles/rails-5.2.gemfile +9 -6
  16. data/gemfiles/rails-6.0.gemfile +14 -0
  17. data/lib/doorkeeper-sequel.rb +21 -17
  18. data/lib/doorkeeper-sequel/gem_version.rb +5 -3
  19. data/lib/doorkeeper-sequel/generators/application_owner_generator.rb +5 -3
  20. data/lib/doorkeeper-sequel/generators/concerns/migration_actions.rb +8 -6
  21. data/lib/doorkeeper-sequel/generators/confidential_applications_generator.rb +5 -3
  22. data/lib/doorkeeper-sequel/generators/migration_generator.rb +5 -3
  23. data/lib/doorkeeper-sequel/generators/pkce_generator.rb +16 -0
  24. data/lib/doorkeeper-sequel/generators/polymorphic_resource_owner_generator.rb +16 -0
  25. data/lib/doorkeeper-sequel/generators/previous_refresh_token_generator.rb +5 -3
  26. data/lib/doorkeeper-sequel/generators/templates/add_confidential_to_application_migration.rb +2 -0
  27. data/lib/doorkeeper-sequel/generators/templates/add_owner_to_application.rb +3 -1
  28. data/lib/doorkeeper-sequel/generators/templates/add_previous_refresh_token_to_access_tokens.rb +3 -1
  29. data/lib/doorkeeper-sequel/generators/templates/create_doorkeeper_tables.rb +7 -3
  30. data/lib/doorkeeper-sequel/generators/templates/enable_pkce_migration.rb +10 -0
  31. data/lib/doorkeeper-sequel/generators/templates/enable_polymorphic_resource_owner_migration.rb +15 -0
  32. data/lib/doorkeeper-sequel/mixins/access_grant_mixin.rb +45 -7
  33. data/lib/doorkeeper-sequel/mixins/access_token_mixin.rb +154 -42
  34. data/lib/doorkeeper-sequel/mixins/application_mixin.rb +116 -22
  35. data/lib/doorkeeper-sequel/mixins/concerns/ownership.rb +2 -0
  36. data/lib/doorkeeper-sequel/mixins/concerns/sequel_compat.rb +33 -3
  37. data/lib/doorkeeper-sequel/railtie.rb +3 -1
  38. data/lib/doorkeeper-sequel/tasks/doorkeeper-sequel.rake +20 -3
  39. data/lib/doorkeeper-sequel/validators/redirect_uri_validator.rb +35 -6
  40. data/lib/doorkeeper-sequel/version.rb +3 -1
  41. data/lib/doorkeeper/orm/sequel.rb +11 -4
  42. data/lib/doorkeeper/orm/sequel/access_grant.rb +18 -0
  43. data/lib/doorkeeper/orm/sequel/access_token.rb +6 -0
  44. data/lib/doorkeeper/orm/sequel/application.rb +34 -3
  45. data/lib/doorkeeper/orm/sequel/stale_records_cleaner.rb +26 -0
  46. data/lib/doorkeeper/redirect_uri_validator.rb +9 -0
  47. data/spec/stubs/config/initializers/db.rb +6 -1
  48. data/spec/stubs/spec_helper_integration.rb +9 -11
  49. metadata +225 -235
  50. data/gemfiles/rails-4.2.gemfile +0 -13
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec path: ".."
6
+
7
+ gem "bcrypt", "~> 3.1"
8
+ gem "rails", "~> 6.0.0", "<= 6.1"
9
+ gem "sequel", ">= 4.0"
10
+ gem "sqlite3", "~> 1.3.5"
11
+
12
+ group :test do
13
+ gem "rspec-rails", "~> 4.0"
14
+ end
@@ -1,28 +1,32 @@
1
- require 'doorkeeper-sequel/version'
1
+ # frozen_string_literal: true
2
2
 
3
- if defined?(::Rails)
4
- require 'doorkeeper-sequel/railtie'
5
- end
3
+ require "doorkeeper-sequel/version"
4
+
5
+ require "doorkeeper-sequel/railtie" if defined?(::Rails)
6
6
 
7
- require 'doorkeeper'
8
- require 'thor/group'
7
+ require "doorkeeper"
8
+ require "doorkeeper/redirect_uri_validator"
9
+ require "doorkeeper/oauth/nonstandard"
10
+ require "thor/group"
9
11
 
10
- require 'doorkeeper-sequel/generators/concerns/migration_actions'
11
- require 'doorkeeper-sequel/generators/application_owner_generator'
12
- require 'doorkeeper-sequel/generators/migration_generator'
13
- require 'doorkeeper-sequel/generators/previous_refresh_token_generator'
12
+ require "doorkeeper-sequel/generators/concerns/migration_actions"
13
+ require "doorkeeper-sequel/generators/application_owner_generator"
14
+ require "doorkeeper-sequel/generators/migration_generator"
15
+ require "doorkeeper-sequel/generators/previous_refresh_token_generator"
16
+ require "doorkeeper-sequel/generators/confidential_applications_generator"
17
+ require "doorkeeper-sequel/generators/polymorphic_resource_owner_generator"
14
18
 
15
- require 'doorkeeper-sequel/mixins/concerns/sequel_compat'
16
- require 'doorkeeper-sequel/mixins/access_token_mixin'
17
- require 'doorkeeper-sequel/mixins/access_grant_mixin'
18
- require 'doorkeeper-sequel/mixins/application_mixin'
19
+ require "doorkeeper-sequel/mixins/concerns/sequel_compat"
20
+ require "doorkeeper-sequel/mixins/access_token_mixin"
21
+ require "doorkeeper-sequel/mixins/access_grant_mixin"
22
+ require "doorkeeper-sequel/mixins/application_mixin"
19
23
 
20
- require 'doorkeeper/orm/sequel'
24
+ require "doorkeeper/orm/sequel"
21
25
 
22
26
  module DoorkeeperSequel
23
27
  def load_locales
24
- locales_dir = File.expand_path('../../config/locales', __FILE__)
25
- locales = Dir[File.join(locales_dir, '*.yml')]
28
+ locales_dir = File.expand_path("../config/locales", __dir__)
29
+ locales = Dir[File.join(locales_dir, "*.yml")]
26
30
 
27
31
  I18n.load_path |= locales
28
32
  end
@@ -1,13 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  def self.gem_version
3
5
  Gem::Version.new VERSION::STRING
4
6
  end
5
7
 
6
8
  module VERSION
7
- MAJOR = 1
8
- MINOR = 5
9
+ MAJOR = 2
10
+ MINOR = 4
9
11
  TINY = 0
10
12
 
11
- STRING = [MAJOR, MINOR, TINY].compact.join('.')
13
+ STRING = [MAJOR, MINOR, TINY].compact.join(".")
12
14
  end
13
15
  end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class ApplicationOwnerGenerator < ::Thor::Group
3
5
  include ::Thor::Actions
4
6
  include MigrationActions
5
7
 
6
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("templates", __dir__)
7
9
 
8
- desc 'Provide support for client application ownership.'
10
+ desc "Provide support for client application ownership."
9
11
 
10
12
  def install
11
- create_migration 'add_owner_to_application.rb'
13
+ create_migration "add_owner_to_application.rb"
12
14
  end
13
15
  end
14
16
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  module MigrationActions
3
5
  extend ::ActiveSupport::Concern
@@ -9,7 +11,7 @@ module DoorkeeperSequel
9
11
  end
10
12
 
11
13
  def migration_template
12
- File.expand_path('../templates/migration.rb', __FILE__)
14
+ File.expand_path("templates/migration.rb", __dir__)
13
15
  end
14
16
 
15
17
  private
@@ -19,20 +21,20 @@ module DoorkeeperSequel
19
21
  end
20
22
 
21
23
  def new_migration_number
22
- current_number = current_migration_number('db/migrate')
24
+ current_number = current_migration_number("db/migrate")
23
25
 
24
26
  # possible numeric migration
25
- if current_number && current_number.start_with?('0')
27
+ if current_number&.start_with?("0")
26
28
  # generate the same name as used by the developer
27
- "%.#{current_number.length}d" % (current_number.to_i + 1)
29
+ format("%.#{current_number.length}d", (current_number.to_i + 1))
28
30
  else
29
- Time.now.utc.strftime('%Y%m%d%H%M%S')
31
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
30
32
  end
31
33
  end
32
34
 
33
35
  def current_migration_number(dirname)
34
36
  migration_lookup_at(dirname).collect do |file|
35
- File.basename(file).split('_').first
37
+ File.basename(file).split("_").first
36
38
  end.max
37
39
  end
38
40
 
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class ConfidentialApplicationsGenerator < ::Thor::Group
3
5
  include ::Thor::Actions
4
6
  include MigrationActions
5
7
 
6
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("templates", __dir__)
7
9
 
8
- desc 'Add confidential column to Doorkeeper applications.'
10
+ desc "Add confidential column to Doorkeeper applications."
9
11
 
10
12
  def install
11
- create_migration 'add_confidential_to_application_migration'
13
+ create_migration "add_confidential_to_application_migration"
12
14
  end
13
15
  end
14
16
  end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class MigrationGenerator < ::Thor::Group
3
5
  include ::Thor::Actions
4
6
  include MigrationActions
5
7
 
6
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("templates", __dir__)
7
9
 
8
- desc 'Installs Doorkeeper Sequel migration file.'
10
+ desc "Installs Doorkeeper Sequel migration file."
9
11
 
10
12
  def install
11
- create_migration 'create_doorkeeper_tables.rb'
13
+ create_migration "create_doorkeeper_tables.rb"
12
14
  end
13
15
  end
14
16
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoorkeeperSequel
4
+ class PkceGenerator < ::Thor::Group
5
+ include ::Thor::Actions
6
+ include MigrationActions
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Provide support for PKCE."
11
+
12
+ def install
13
+ create_migration "enable_pkce_migration.rb.erb"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoorkeeperSequel
4
+ class PolymorphicResourceOwnerGenerator < ::Thor::Group
5
+ include ::Thor::Actions
6
+ include MigrationActions
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ desc "Provide support for Polymorphic Resource Owner."
11
+
12
+ def install
13
+ create_migration "enable_polymorphic_resource_owner_migration.rb.erb"
14
+ end
15
+ end
16
+ end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class PreviousRefreshTokenGenerator < ::Thor::Group
3
5
  include ::Thor::Actions
4
6
  include MigrationActions
5
7
 
6
- source_root File.expand_path('../templates', __FILE__)
8
+ source_root File.expand_path("templates", __dir__)
7
9
 
8
- desc 'Support revoke refresh token on access token use'
10
+ desc "Support revoke refresh token on access token use"
9
11
 
10
12
  def install
11
- create_migration 'add_previous_refresh_token_to_access_tokens.rb'
13
+ create_migration "add_previous_refresh_token_to_access_tokens.rb"
12
14
  end
13
15
  end
14
16
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Sequel.migration do
2
4
  change do
3
5
  alter_table(:oauth_applications) do
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Sequel.migration do
2
4
  change do
3
5
  alter_table(:oauth_applications) do
4
6
  add_column :owner_id, Integer, null: true
5
7
  add_column :owner_type, String, null: true
6
- add_index [:owner_id, :owner_type]
8
+ add_index %i[owner_id owner_type]
7
9
  end
8
10
  end
9
11
  end
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Sequel.migration do
2
4
  change do
3
5
  alter_table(:oauth_access_tokens) do
4
- add_column :previous_refresh_token, String, default: '', null: false
6
+ add_column :previous_refresh_token, String, default: "", null: false
5
7
  end
6
8
  end
7
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Sequel.migration do
2
4
  change do
3
5
  create_table :oauth_applications do
@@ -7,7 +9,7 @@ Sequel.migration do
7
9
  column :uid, String, size: 255, null: false, index: { unique: true }
8
10
  column :secret, String, size: 255, null: false
9
11
 
10
- column :scopes, String, size: 255, null: false, default: ''
12
+ column :scopes, String, size: 255, null: false, default: ""
11
13
  column :redirect_uri, String
12
14
  column :confidential, TrueClass, null: false, default: true
13
15
 
@@ -19,7 +21,8 @@ Sequel.migration do
19
21
  primary_key :id
20
22
  foreign_key :application_id, :oauth_applications, null: false, on_delete: :cascade
21
23
 
22
- column :resource_owner_id, Integer, null: false
24
+ column :resource_owner_id, Integer, null: false, index: true
25
+ column :resource_owner_type, String, null: false, index: true
23
26
 
24
27
  column :token, String, size: 255, null: false, index: { unique: true }
25
28
  column :expires_in, Integer, null: false
@@ -34,6 +37,7 @@ Sequel.migration do
34
37
  foreign_key :application_id, :oauth_applications, null: false, on_delete: :cascade
35
38
 
36
39
  column :resource_owner_id, Integer, index: true
40
+ column :resource_owner_type, String, index: true
37
41
 
38
42
  # If you use a custom token generator you may need to change this column
39
43
  # from string to text, so that it accepts tokens larger than 255
@@ -50,7 +54,7 @@ Sequel.migration do
50
54
  # previous tokens are revoked as soon as a new access token is created.
51
55
  # Comment out this line if you'd rather have refresh tokens
52
56
  # instantly revoked.
53
- column :previous_refresh_token, String, size: 255, null: false, default: ''
57
+ column :previous_refresh_token, String, size: 255, null: false, default: ""
54
58
  column :expires_in, Integer
55
59
  column :revoked_at, DateTime
56
60
  column :created_at, DateTime, null: false
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table(:oauth_access_grants) do
6
+ add_column :code_challenge, String, null: true
7
+ add_column :code_challenge_method, String, null: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ Sequel.migration do
4
+ change do
5
+ alter_table(:oauth_access_grants) do
6
+ add_column :resource_owner_type, String, default: nil
7
+ add_index [:resource_owner_id, :resource_owner_type]
8
+ end
9
+
10
+ alter_table(:oauth_access_tokens) do
11
+ add_column :resource_owner_type, String, default: nil
12
+ add_index [:resource_owner_id, :resource_owner_type]
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  module AccessGrantMixin
3
5
  extend ActiveSupport::Concern
@@ -7,16 +9,17 @@ module DoorkeeperSequel
7
9
  include Doorkeeper::Models::Expirable
8
10
  include Doorkeeper::Models::Revocable
9
11
  include Doorkeeper::Models::Accessible
12
+ include Doorkeeper::Models::SecretStorable
10
13
  include Doorkeeper::Models::Scopes
11
14
 
12
15
  included do
13
16
  plugin :validation_helpers
14
17
  plugin :timestamps
15
-
16
- many_to_one :application, class: 'Doorkeeper::Application'
18
+ many_to_one :application, class: "Doorkeeper::Application"
17
19
 
18
20
  set_allowed_columns :resource_owner_id, :application_id,
19
- :expires_in, :redirect_uri, :scopes
21
+ :expires_in, :redirect_uri, :scopes, :code_challenge, :code_challenge_method,
22
+ :token
20
23
 
21
24
  def before_validation
22
25
  generate_token if new?
@@ -25,22 +28,57 @@ module DoorkeeperSequel
25
28
 
26
29
  def validate
27
30
  super
28
- validates_presence [:resource_owner_id, :application_id,
29
- :token, :expires_in, :redirect_uri]
31
+ validates_presence %i[resource_owner_id application_id
32
+ token expires_in redirect_uri]
30
33
  validates_unique [:token]
31
34
  end
32
35
  end
33
36
 
34
37
  module ClassMethods
35
38
  def by_token(token)
36
- first(token: token.to_s)
39
+ find_by_plaintext_token(:token, token)
40
+ end
41
+
42
+ def find_by(params)
43
+ first(params)
44
+ end
45
+
46
+ def revoke_all_for(application_id, resource_owner, clock = Time)
47
+ where(application_id: application_id,
48
+ resource_owner_id: resource_owner.id,
49
+ revoked_at: nil)
50
+ .update(revoked_at: clock.now.utc)
51
+ end
52
+
53
+ def generate_code_challenge(code_verifier)
54
+ padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
55
+ padded_result.split("=")[0] # Remove any trailing '='
56
+ end
57
+
58
+ def pkce_supported?
59
+ new.pkce_supported?
60
+ end
61
+
62
+ def secret_strategy
63
+ ::Doorkeeper.configuration.token_secret_strategy
64
+ end
65
+
66
+ def fallback_secret_strategy
67
+ ::Doorkeeper.configuration.token_secret_fallback_strategy
37
68
  end
38
69
  end
39
70
 
40
71
  private
41
72
 
73
+ # Generates token value with UniqueToken class.
74
+ #
75
+ # @return [String] token value
76
+ #
42
77
  def generate_token
43
- self.token = UniqueToken.generate
78
+ return nil unless self[:token].nil?
79
+
80
+ @raw_token = UniqueToken.generate
81
+ secret_strategy.store_secret(self, :token, @raw_token)
44
82
  end
45
83
  end
46
84
  end
@@ -1,24 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  module AccessTokenMixin
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  include SequelCompat
6
8
  include Doorkeeper::OAuth::Helpers
7
- include Doorkeeper::Models::Expirable
8
9
  include Doorkeeper::Models::Revocable
10
+ include Doorkeeper::Models::Expirable
11
+ include Doorkeeper::Models::Reusable
9
12
  include Doorkeeper::Models::Accessible
13
+ include Doorkeeper::Models::SecretStorable
10
14
  include Doorkeeper::Models::Scopes
15
+ include Doorkeeper::Models::ResourceOwnerable
11
16
 
12
17
  included do
13
18
  plugin :validation_helpers
14
19
  plugin :timestamps
15
20
 
16
- many_to_one :application, class: 'Doorkeeper::Application'
21
+
22
+ many_to_one :application, class: "Doorkeeper::Application"
23
+ many_to_one :resource_owner, polymorphic: true
17
24
 
18
25
  attr_writer :use_refresh_token
19
26
 
20
- set_allowed_columns :application_id, :resource_owner_id, :expires_in,
21
- :scopes, :use_refresh_token, :previous_refresh_token
27
+ set_allowed_columns :application_id, :resource_owner_id, :resource_owner_type,
28
+ :expires_in, :scopes, :use_refresh_token,
29
+ :previous_refresh_token, :token, :refresh_token
22
30
 
23
31
  def before_validation
24
32
  if new?
@@ -44,11 +52,15 @@ module DoorkeeperSequel
44
52
 
45
53
  module ClassMethods
46
54
  def by_token(token)
47
- first(token: token.to_s)
55
+ find_by_plaintext_token(:token, token)
56
+ end
57
+
58
+ def find_by(params)
59
+ first(params)
48
60
  end
49
61
 
50
62
  def by_refresh_token(refresh_token)
51
- first(refresh_token: refresh_token.to_s)
63
+ find_by_plaintext_token(:refresh_token, refresh_token)
52
64
  end
53
65
 
54
66
  def revoke_all_for(application_id, resource_owner, clock = Time)
@@ -57,6 +69,10 @@ module DoorkeeperSequel
57
69
  revoked_at: nil)
58
70
  .update(revoked_at: clock.now.utc)
59
71
  end
72
+
73
+ def by_previous_refresh_token(previous_refresh_token)
74
+ where(refresh_token: previous_refresh_token).first
75
+ end
60
76
 
61
77
  def matching_token_for(application, resource_owner_or_id, scopes)
62
78
  resource_owner_id = if resource_owner_or_id.respond_to?(:to_key)
@@ -64,63 +80,127 @@ module DoorkeeperSequel
64
80
  else
65
81
  resource_owner_or_id
66
82
  end
67
- token = last_authorized_token_for(application.try(:id), resource_owner_id)
68
- if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
69
- token
83
+ tokens = authorized_tokens_for(application.try(:id), resource_owner_id).all
84
+ tokens.detect do |token|
85
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
70
86
  end
71
87
  end
72
88
 
89
+ def find_matching_token(relation, application, scopes)
90
+ return nil unless relation
91
+
92
+ matching_tokens = []
93
+
94
+ tokens = relation.select do |token|
95
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
96
+ end
97
+
98
+ matching_tokens.concat(tokens)
99
+ matching_tokens.max_by(&:created_at)
100
+ end
101
+
73
102
  def scopes_match?(token_scopes, param_scopes, app_scopes)
74
- (token_scopes.blank? && param_scopes.blank?) ||
75
- Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
76
- token_scopes.to_s,
77
- param_scopes,
78
- app_scopes
103
+ return true if token_scopes.empty? && param_scopes.empty?
104
+
105
+ (token_scopes.sort == param_scopes.sort) &&
106
+ Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
107
+ scope_str: param_scopes.to_s,
108
+ server_scopes: Doorkeeper.configuration.scopes,
109
+ app_scopes: app_scopes
79
110
  )
80
111
  end
81
112
 
82
- def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
113
+ def authorized_tokens_for(application_id, resource_owner)
114
+ by_resource_owner(resource_owner).where(application_id: application_id,
115
+ revoked_at: nil).order(Sequel.desc(:created_at))
116
+ end
117
+
118
+ def find_or_create_for(*args)
119
+ attributes = if args.size > 1
120
+ {
121
+ application: args[0],
122
+ resource_owner: args[1],
123
+ scopes: args[2],
124
+ expires_in: args[3],
125
+ use_refresh_token: args[4],
126
+ }
127
+ else
128
+ args.first
129
+ end
130
+
131
+ application = attributes[:application]
132
+ resource_owner = attributes[:resource_owner]
133
+ scopes = attributes[:scopes]
134
+ expires_in = attributes[:expires_in]
135
+ use_refresh_token = attributes[:use_refresh_token]
136
+
83
137
  if Doorkeeper.configuration.reuse_access_token
84
- access_token = matching_token_for(application, resource_owner_id, scopes)
85
- return access_token if access_token && !access_token.expired?
138
+ access_token = matching_token_for(application, resource_owner, scopes)
139
+ return access_token if access_token&.reusable?
86
140
  end
87
-
88
- create!(
89
- application_id: application.try(:id),
90
- resource_owner_id: resource_owner_id,
91
- scopes: scopes.to_s,
141
+
142
+ create_for(
143
+ application: application,
144
+ resource_owner: resource_owner,
145
+ scopes: scopes,
92
146
  expires_in: expires_in,
93
- use_refresh_token: use_refresh_token
147
+ use_refresh_token: use_refresh_token,
94
148
  )
95
149
  end
150
+
151
+ def create_for(application:, resource_owner:, scopes:, **token_attributes)
152
+ token_attributes[:application_id] = application&.id
153
+ token_attributes[:scopes] = scopes.to_s
154
+
155
+ if Doorkeeper.config.polymorphic_resource_owner?
156
+ token_attributes[:resource_owner] = resource_owner
157
+ else
158
+ token_attributes[:resource_owner_id] = resource_owner_id_for(resource_owner)
159
+ end
160
+
161
+ create!(token_attributes)
162
+ end
96
163
 
97
164
  def last_authorized_token_for(application_id, resource_owner_id)
98
- where(application_id: application_id,
99
- resource_owner_id: resource_owner_id,
100
- revoked_at: nil)
101
- .send(order_method, created_at_desc)
102
- .first
165
+ authorized_tokens_for(application_id, resource_owner_id).first
166
+ end
167
+
168
+ def secret_strategy
169
+ ::Doorkeeper.configuration.token_secret_strategy
170
+ end
171
+
172
+ def fallback_secret_strategy
173
+ ::Doorkeeper.configuration.token_secret_fallback_strategy
174
+ end
175
+
176
+ def polymorphic_resource_owner?
177
+ columns.include?(:resource_owner_type)
103
178
  end
104
179
  end
105
180
 
106
181
  def token_type
107
- 'Bearer'
182
+ "Bearer"
108
183
  end
109
184
 
110
185
  def use_refresh_token?
186
+ @use_refresh_token ||= false
111
187
  !!@use_refresh_token
112
188
  end
113
189
 
114
190
  def as_json(_options = {})
115
191
  {
116
192
  resource_owner_id: resource_owner_id,
117
- scopes: scopes,
118
- expires_in_seconds: expires_in_seconds,
193
+ scope: scopes,
194
+ expires_in: expires_in_seconds,
119
195
  application: { uid: application.try(:uid) },
120
- created_at: created_at.to_i
121
- }
196
+ created_at: created_at.to_i,
197
+ }.tap do |json|
198
+ if Doorkeeper.configuration.polymorphic_resource_owner?
199
+ json[:resource_owner_type] = resource_owner_type
200
+ end
201
+ end
122
202
  end
123
-
203
+
124
204
  # It indicates whether the tokens have the same credential
125
205
  def same_credential?(access_token)
126
206
  application_id == access_token.application_id &&
@@ -131,32 +211,64 @@ module DoorkeeperSequel
131
211
  accessible? && includes_scope?(*scopes)
132
212
  end
133
213
 
214
+ def plaintext_refresh_token
215
+ if secret_strategy.allows_restoring_secrets?
216
+ secret_strategy.restore_secret(self, :refresh_token)
217
+ else
218
+ @raw_refresh_token
219
+ end
220
+ end
221
+
222
+ def plaintext_token
223
+ if secret_strategy.allows_restoring_secrets?
224
+ secret_strategy.restore_secret(self, :token)
225
+ else
226
+ @raw_token
227
+ end
228
+ end
229
+
230
+ # Revokes token with `:refresh_token` equal to `:previous_refresh_token`
231
+ # and clears `:previous_refresh_token` attribute.
232
+ #
233
+ def revoke_previous_refresh_token!
234
+ return unless self.class.refresh_token_revoked_on_use?
235
+
236
+ old_refresh_token&.revoke
237
+ update(previous_refresh_token: "")
238
+ end
239
+
134
240
  private
135
241
 
242
+ def old_refresh_token
243
+ @old_refresh_token ||= self.class.by_previous_refresh_token(previous_refresh_token)
244
+ end
245
+
136
246
  def generate_refresh_token
137
- self[:refresh_token] = UniqueToken.generate
247
+ @raw_refresh_token = UniqueToken.generate
248
+ secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token)
138
249
  end
139
250
 
140
251
  def generate_token
141
252
  self[:created_at] ||= Time.now.utc
142
253
 
143
- generator = token_generator
144
- unless generator.respond_to?(:generate)
145
- raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
146
- end
147
-
148
- self[:token] = generator.generate(
254
+ @raw_token = token_generator.generate(
149
255
  resource_owner_id: resource_owner_id,
150
256
  scopes: scopes,
151
257
  application: application,
152
258
  expires_in: expires_in,
153
259
  created_at: created_at
154
260
  )
261
+ secret_strategy.store_secret(self, :token, @raw_token)
262
+ @raw_token
155
263
  end
156
264
 
157
265
  def token_generator
158
266
  generator_name = Doorkeeper.configuration.access_token_generator
159
- generator_name.constantize
267
+ generator = generator_name.constantize
268
+
269
+ return generator if generator.respond_to?(:generate)
270
+
271
+ raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
160
272
  rescue NameError
161
273
  raise Doorkeeper::Errors::TokenGeneratorNotFound, "#{generator_name} not found"
162
274
  end