doorkeeper-sequel 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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