doorkeeper-sequel 2.0.0 → 2.1.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 (41) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +29 -2
  3. data/.travis.yml +3 -12
  4. data/Gemfile +13 -9
  5. data/Rakefile +22 -20
  6. data/config/locales/en.yml +8 -0
  7. data/doorkeeper-sequel.gemspec +27 -24
  8. data/gemfiles/rails-5.0.gemfile +9 -6
  9. data/gemfiles/rails-5.1.gemfile +9 -6
  10. data/gemfiles/rails-5.2.gemfile +9 -6
  11. data/gemfiles/rails-6.0.gemfile +18 -0
  12. data/lib/doorkeeper-sequel.rb +20 -17
  13. data/lib/doorkeeper-sequel/gem_version.rb +4 -2
  14. data/lib/doorkeeper-sequel/generators/application_owner_generator.rb +5 -3
  15. data/lib/doorkeeper-sequel/generators/concerns/migration_actions.rb +8 -6
  16. data/lib/doorkeeper-sequel/generators/confidential_applications_generator.rb +5 -3
  17. data/lib/doorkeeper-sequel/generators/migration_generator.rb +5 -3
  18. data/lib/doorkeeper-sequel/generators/pkce_generator.rb +5 -3
  19. data/lib/doorkeeper-sequel/generators/previous_refresh_token_generator.rb +5 -3
  20. data/lib/doorkeeper-sequel/generators/templates/add_confidential_to_application_migration.rb +2 -0
  21. data/lib/doorkeeper-sequel/generators/templates/add_owner_to_application.rb +3 -1
  22. data/lib/doorkeeper-sequel/generators/templates/add_previous_refresh_token_to_access_tokens.rb +3 -1
  23. data/lib/doorkeeper-sequel/generators/templates/create_doorkeeper_tables.rb +4 -2
  24. data/lib/doorkeeper-sequel/generators/templates/enable_pkce_migration.rb +3 -1
  25. data/lib/doorkeeper-sequel/mixins/access_grant_mixin.rb +30 -16
  26. data/lib/doorkeeper-sequel/mixins/access_token_mixin.rb +73 -24
  27. data/lib/doorkeeper-sequel/mixins/application_mixin.rb +52 -25
  28. data/lib/doorkeeper-sequel/mixins/concerns/ownership.rb +2 -0
  29. data/lib/doorkeeper-sequel/mixins/concerns/sequel_compat.rb +6 -4
  30. data/lib/doorkeeper-sequel/railtie.rb +3 -1
  31. data/lib/doorkeeper-sequel/tasks/doorkeeper-sequel.rake +7 -5
  32. data/lib/doorkeeper-sequel/validators/redirect_uri_validator.rb +29 -6
  33. data/lib/doorkeeper-sequel/version.rb +3 -1
  34. data/lib/doorkeeper/orm/sequel.rb +9 -5
  35. data/lib/doorkeeper/orm/sequel/access_grant.rb +18 -0
  36. data/lib/doorkeeper/orm/sequel/access_token.rb +2 -0
  37. data/lib/doorkeeper/orm/sequel/application.rb +26 -3
  38. data/lib/doorkeeper/redirect_uri_validator.rb +9 -0
  39. data/spec/stubs/spec_helper_integration.rb +8 -8
  40. metadata +177 -245
  41. data/gemfiles/rails-4.2.gemfile +0 -13
@@ -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
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class PkceGenerator < ::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 PKCE.'
10
+ desc "Provide support for PKCE."
9
11
 
10
12
  def install
11
- create_migration 'enable_pkce_migration.rb.erb'
13
+ create_migration "enable_pkce_migration.rb.erb"
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 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
 
@@ -50,7 +52,7 @@ Sequel.migration do
50
52
  # previous tokens are revoked as soon as a new access token is created.
51
53
  # Comment out this line if you'd rather have refresh tokens
52
54
  # instantly revoked.
53
- column :previous_refresh_token, String, size: 255, null: false, default: ''
55
+ column :previous_refresh_token, String, size: 255, null: false, default: ""
54
56
  column :expires_in, Integer
55
57
  column :revoked_at, DateTime
56
58
  column :created_at, DateTime, null: false
@@ -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_grants) do
4
- add_column :code_challenge, Integer, null: true
6
+ add_column :code_challenge, String, null: true
5
7
  add_column :code_challenge_method, String, null: true
6
8
  end
7
9
  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, :code_challenge, :code_challenge_method
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,23 +28,19 @@ 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
-
33
- def uses_pkce?
34
- pkce_supported? && code_challenge.present?
35
- end
36
-
37
- def pkce_supported?
38
- respond_to? :code_challenge
39
- end
40
35
  end
41
36
 
42
37
  module ClassMethods
43
38
  def by_token(token)
44
- first(token: token.to_s)
39
+ find_by_plaintext_token(:token, token)
40
+ end
41
+
42
+ def find_by(params)
43
+ first(params)
45
44
  end
46
45
 
47
46
  def revoke_all_for(application_id, resource_owner, clock = Time)
@@ -53,18 +52,33 @@ module DoorkeeperSequel
53
52
 
54
53
  def generate_code_challenge(code_verifier)
55
54
  padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
56
- padded_result.split('=')[0] # Remove any trailing '='
55
+ padded_result.split("=")[0] # Remove any trailing '='
57
56
  end
58
57
 
59
58
  def pkce_supported?
60
59
  new.pkce_supported?
61
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
68
+ end
62
69
  end
63
70
 
64
71
  private
65
72
 
73
+ # Generates token value with UniqueToken class.
74
+ #
75
+ # @return [String] token value
76
+ #
66
77
  def generate_token
67
- 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)
68
82
  end
69
83
  end
70
84
  end
@@ -1,24 +1,29 @@
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
11
15
 
12
16
  included do
13
17
  plugin :validation_helpers
14
18
  plugin :timestamps
15
19
 
16
- many_to_one :application, class: 'Doorkeeper::Application'
20
+ many_to_one :application, class: "Doorkeeper::Application"
17
21
 
18
22
  attr_writer :use_refresh_token
19
23
 
20
24
  set_allowed_columns :application_id, :resource_owner_id, :expires_in,
21
- :scopes, :use_refresh_token, :previous_refresh_token
25
+ :scopes, :use_refresh_token, :previous_refresh_token,
26
+ :token, :refresh_token
22
27
 
23
28
  def before_validation
24
29
  if new?
@@ -44,11 +49,15 @@ module DoorkeeperSequel
44
49
 
45
50
  module ClassMethods
46
51
  def by_token(token)
47
- first(token: token.to_s)
52
+ find_by_plaintext_token(:token, token)
53
+ end
54
+
55
+ def find_by(params)
56
+ first(params)
48
57
  end
49
58
 
50
59
  def by_refresh_token(refresh_token)
51
- first(refresh_token: refresh_token.to_s)
60
+ find_by_plaintext_token(:refresh_token, refresh_token)
52
61
  end
53
62
 
54
63
  def revoke_all_for(application_id, resource_owner, clock = Time)
@@ -64,33 +73,46 @@ module DoorkeeperSequel
64
73
  else
65
74
  resource_owner_or_id
66
75
  end
67
- tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
76
+ tokens = authorized_tokens_for(application.try(:id), resource_owner_id).all
68
77
  tokens.detect do |token|
69
78
  scopes_match?(token.scopes, scopes, application.try(:scopes))
70
79
  end
71
80
  end
72
81
 
82
+ def find_matching_token(relation, application, scopes)
83
+ return nil unless relation
84
+
85
+ matching_tokens = []
86
+
87
+ tokens = relation.select do |token|
88
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
89
+ end
90
+
91
+ matching_tokens.concat(tokens)
92
+ matching_tokens.max_by(&:created_at)
93
+ end
94
+
73
95
  def scopes_match?(token_scopes, param_scopes, app_scopes)
74
96
  return true if token_scopes.empty? && param_scopes.empty?
97
+
75
98
  (token_scopes.sort == param_scopes.sort) &&
76
99
  Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
77
- param_scopes.to_s,
78
- Doorkeeper.configuration.scopes,
79
- app_scopes
100
+ scope_str: param_scopes.to_s,
101
+ server_scopes: Doorkeeper.configuration.scopes,
102
+ app_scopes: app_scopes
80
103
  )
81
104
  end
82
105
 
83
106
  def authorized_tokens_for(application_id, resource_owner_id)
84
- ordered_by(:created_at, :desc).
85
- where(application_id: application_id,
86
- resource_owner_id: resource_owner_id,
87
- revoked_at: nil)
107
+ where(application_id: application_id,
108
+ resource_owner_id: resource_owner_id,
109
+ revoked_at: nil).order(Sequel.desc(:created_at))
88
110
  end
89
111
 
90
112
  def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
91
113
  if Doorkeeper.configuration.reuse_access_token
92
114
  access_token = matching_token_for(application, resource_owner_id, scopes)
93
- return access_token if access_token && !access_token.expired?
115
+ return access_token if access_token&.reusable?
94
116
  end
95
117
 
96
118
  create!(
@@ -105,13 +127,22 @@ module DoorkeeperSequel
105
127
  def last_authorized_token_for(application_id, resource_owner_id)
106
128
  authorized_tokens_for(application_id, resource_owner_id).first
107
129
  end
130
+
131
+ def secret_strategy
132
+ ::Doorkeeper.configuration.token_secret_strategy
133
+ end
134
+
135
+ def fallback_secret_strategy
136
+ ::Doorkeeper.configuration.token_secret_fallback_strategy
137
+ end
108
138
  end
109
139
 
110
140
  def token_type
111
- 'Bearer'
141
+ "Bearer"
112
142
  end
113
143
 
114
144
  def use_refresh_token?
145
+ @use_refresh_token ||= false
115
146
  !!@use_refresh_token
116
147
  end
117
148
 
@@ -121,7 +152,7 @@ module DoorkeeperSequel
121
152
  scope: scopes,
122
153
  expires_in: expires_in_seconds,
123
154
  application: { uid: application.try(:uid) },
124
- created_at: created_at.to_i
155
+ created_at: created_at.to_i,
125
156
  }
126
157
  end
127
158
 
@@ -135,32 +166,50 @@ module DoorkeeperSequel
135
166
  accessible? && includes_scope?(*scopes)
136
167
  end
137
168
 
169
+ def plaintext_refresh_token
170
+ if secret_strategy.allows_restoring_secrets?
171
+ secret_strategy.restore_secret(self, :refresh_token)
172
+ else
173
+ @raw_refresh_token
174
+ end
175
+ end
176
+
177
+ def plaintext_token
178
+ if secret_strategy.allows_restoring_secrets?
179
+ secret_strategy.restore_secret(self, :token)
180
+ else
181
+ @raw_token
182
+ end
183
+ end
184
+
138
185
  private
139
186
 
140
187
  def generate_refresh_token
141
- self[:refresh_token] = UniqueToken.generate
188
+ @raw_refresh_token = UniqueToken.generate
189
+ secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token)
142
190
  end
143
191
 
144
192
  def generate_token
145
193
  self[:created_at] ||= Time.now.utc
146
194
 
147
- generator = token_generator
148
- unless generator.respond_to?(:generate)
149
- raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
150
- end
151
-
152
- self[:token] = generator.generate(
195
+ @raw_token = token_generator.generate(
153
196
  resource_owner_id: resource_owner_id,
154
197
  scopes: scopes,
155
198
  application: application,
156
199
  expires_in: expires_in,
157
200
  created_at: created_at
158
201
  )
202
+ secret_strategy.store_secret(self, :token, @raw_token)
203
+ @raw_token
159
204
  end
160
205
 
161
206
  def token_generator
162
207
  generator_name = Doorkeeper.configuration.access_token_generator
163
- generator_name.constantize
208
+ generator = generator_name.constantize
209
+
210
+ return generator if generator.respond_to?(:generate)
211
+
212
+ raise Doorkeeper::Errors::UnableToGenerateToken, "#{generator} does not respond to `.generate`."
164
213
  rescue NameError
165
214
  raise Doorkeeper::Errors::TokenGeneratorNotFound, "#{generator_name} not found"
166
215
  end