doorkeeper 5.0.0 → 5.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 (241) hide show
  1. checksums.yaml +5 -5
  2. data/.hound.yml +2 -1
  3. data/.rubocop.yml +37 -4
  4. data/.travis.yml +16 -21
  5. data/Appraisals +29 -7
  6. data/Dangerfile +67 -0
  7. data/Gemfile +19 -5
  8. data/NEWS.md +107 -10
  9. data/README.md +75 -470
  10. data/Rakefile +10 -8
  11. data/app/controllers/doorkeeper/application_controller.rb +3 -2
  12. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -13
  13. data/app/controllers/doorkeeper/applications_controller.rb +21 -7
  14. data/app/controllers/doorkeeper/authorizations_controller.rb +3 -3
  15. data/app/controllers/doorkeeper/authorized_applications_controller.rb +2 -0
  16. data/app/controllers/doorkeeper/token_info_controller.rb +4 -2
  17. data/app/controllers/doorkeeper/tokens_controller.rb +13 -11
  18. data/app/helpers/doorkeeper/dashboard_helper.rb +3 -1
  19. data/app/validators/redirect_uri_validator.rb +7 -2
  20. data/app/views/doorkeeper/applications/_form.html.erb +6 -0
  21. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  22. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  23. data/bin/console +16 -0
  24. data/config/locales/en.yml +1 -0
  25. data/doorkeeper.gemspec +28 -24
  26. data/gemfiles/rails_5_0.gemfile +9 -4
  27. data/gemfiles/rails_5_1.gemfile +9 -4
  28. data/gemfiles/rails_5_2.gemfile +10 -5
  29. data/gemfiles/rails_6_0.gemfile +17 -0
  30. data/gemfiles/rails_master.gemfile +9 -9
  31. data/lib/doorkeeper/config/option.rb +64 -0
  32. data/lib/doorkeeper/config.rb +197 -76
  33. data/lib/doorkeeper/engine.rb +3 -1
  34. data/lib/doorkeeper/errors.rb +17 -0
  35. data/lib/doorkeeper/grape/authorization_decorator.rb +6 -4
  36. data/lib/doorkeeper/grape/helpers.rb +5 -3
  37. data/lib/doorkeeper/helpers/controller.rb +5 -2
  38. data/lib/doorkeeper/models/access_grant_mixin.rb +25 -6
  39. data/lib/doorkeeper/models/access_token_mixin.rb +68 -22
  40. data/lib/doorkeeper/models/application_mixin.rb +45 -1
  41. data/lib/doorkeeper/models/concerns/accessible.rb +2 -0
  42. data/lib/doorkeeper/models/concerns/expirable.rb +6 -2
  43. data/lib/doorkeeper/models/concerns/orderable.rb +2 -0
  44. data/lib/doorkeeper/models/concerns/ownership.rb +3 -6
  45. data/lib/doorkeeper/models/concerns/reusable.rb +19 -0
  46. data/lib/doorkeeper/models/concerns/revocable.rb +4 -1
  47. data/lib/doorkeeper/models/concerns/scopes.rb +7 -1
  48. data/lib/doorkeeper/models/concerns/secret_storable.rb +108 -0
  49. data/lib/doorkeeper/oauth/authorization/code.rb +4 -2
  50. data/lib/doorkeeper/oauth/authorization/context.rb +2 -0
  51. data/lib/doorkeeper/oauth/authorization/token.rb +14 -7
  52. data/lib/doorkeeper/oauth/authorization/uri_builder.rb +3 -1
  53. data/lib/doorkeeper/oauth/authorization_code_request.rb +8 -4
  54. data/lib/doorkeeper/oauth/base_request.rb +2 -0
  55. data/lib/doorkeeper/oauth/base_response.rb +2 -0
  56. data/lib/doorkeeper/oauth/client/credentials.rb +2 -0
  57. data/lib/doorkeeper/oauth/client.rb +3 -1
  58. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -0
  59. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +2 -0
  60. data/lib/doorkeeper/oauth/client_credentials/validation.rb +7 -4
  61. data/lib/doorkeeper/oauth/client_credentials_request.rb +3 -1
  62. data/lib/doorkeeper/oauth/code_request.rb +2 -0
  63. data/lib/doorkeeper/oauth/code_response.rb +4 -2
  64. data/lib/doorkeeper/oauth/error.rb +2 -0
  65. data/lib/doorkeeper/oauth/error_response.rb +20 -6
  66. data/lib/doorkeeper/oauth/forbidden_token_response.rb +10 -3
  67. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +25 -8
  68. data/lib/doorkeeper/oauth/helpers/unique_token.rb +15 -1
  69. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +42 -3
  70. data/lib/doorkeeper/oauth/invalid_token_response.rb +22 -0
  71. data/lib/doorkeeper/oauth/password_access_token_request.rb +9 -2
  72. data/lib/doorkeeper/oauth/pre_authorization.rb +13 -5
  73. data/lib/doorkeeper/oauth/refresh_token_request.rb +15 -4
  74. data/lib/doorkeeper/oauth/scopes.rb +7 -3
  75. data/lib/doorkeeper/oauth/token.rb +4 -2
  76. data/lib/doorkeeper/oauth/token_introspection.rb +76 -8
  77. data/lib/doorkeeper/oauth/token_request.rb +2 -0
  78. data/lib/doorkeeper/oauth/token_response.rb +11 -9
  79. data/lib/doorkeeper/oauth.rb +5 -5
  80. data/lib/doorkeeper/orm/active_record/access_grant.rb +27 -13
  81. data/lib/doorkeeper/orm/active_record/access_token.rb +6 -13
  82. data/lib/doorkeeper/orm/active_record/application.rb +26 -7
  83. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +10 -3
  84. data/lib/doorkeeper/orm/active_record.rb +8 -6
  85. data/lib/doorkeeper/rails/helpers.rb +5 -1
  86. data/lib/doorkeeper/rails/routes/mapper.rb +2 -0
  87. data/lib/doorkeeper/rails/routes/mapping.rb +9 -7
  88. data/lib/doorkeeper/rails/routes.rb +19 -12
  89. data/lib/doorkeeper/rake/db.rake +17 -17
  90. data/lib/doorkeeper/rake.rb +1 -1
  91. data/lib/doorkeeper/request/authorization_code.rb +2 -0
  92. data/lib/doorkeeper/request/client_credentials.rb +2 -0
  93. data/lib/doorkeeper/request/code.rb +2 -0
  94. data/lib/doorkeeper/request/password.rb +2 -0
  95. data/lib/doorkeeper/request/refresh_token.rb +2 -0
  96. data/lib/doorkeeper/request/strategy.rb +2 -0
  97. data/lib/doorkeeper/request/token.rb +2 -0
  98. data/lib/doorkeeper/request.rb +3 -1
  99. data/lib/doorkeeper/secret_storing/base.rb +64 -0
  100. data/lib/doorkeeper/secret_storing/bcrypt.rb +60 -0
  101. data/lib/doorkeeper/secret_storing/plain.rb +33 -0
  102. data/lib/doorkeeper/secret_storing/sha256_hash.rb +26 -0
  103. data/lib/doorkeeper/server.rb +2 -0
  104. data/lib/doorkeeper/stale_records_cleaner.rb +20 -0
  105. data/lib/doorkeeper/validations.rb +2 -0
  106. data/lib/doorkeeper/version.rb +4 -2
  107. data/lib/doorkeeper.rb +69 -59
  108. data/lib/generators/doorkeeper/application_owner_generator.rb +10 -9
  109. data/lib/generators/doorkeeper/confidential_applications_generator.rb +10 -9
  110. data/lib/generators/doorkeeper/install_generator.rb +11 -9
  111. data/lib/generators/doorkeeper/migration_generator.rb +9 -9
  112. data/lib/generators/doorkeeper/pkce_generator.rb +10 -9
  113. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +10 -9
  114. data/lib/generators/doorkeeper/templates/initializer.rb +138 -13
  115. data/lib/generators/doorkeeper/templates/migration.rb.erb +17 -10
  116. data/lib/generators/doorkeeper/views_generator.rb +6 -4
  117. data/spec/controllers/application_metal_controller_spec.rb +24 -10
  118. data/spec/controllers/applications_controller_spec.rb +76 -78
  119. data/spec/controllers/authorizations_controller_spec.rb +189 -142
  120. data/spec/controllers/protected_resources_controller_spec.rb +111 -67
  121. data/spec/controllers/token_info_controller_spec.rb +14 -12
  122. data/spec/controllers/tokens_controller_spec.rb +170 -112
  123. data/spec/dummy/Rakefile +3 -1
  124. data/spec/dummy/app/controllers/application_controller.rb +3 -1
  125. data/spec/dummy/app/controllers/custom_authorizations_controller.rb +3 -1
  126. data/spec/dummy/app/controllers/full_protected_resources_controller.rb +4 -2
  127. data/spec/dummy/app/controllers/home_controller.rb +6 -5
  128. data/spec/dummy/app/controllers/metal_controller.rb +2 -0
  129. data/spec/dummy/app/controllers/semi_protected_resources_controller.rb +4 -2
  130. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  131. data/spec/dummy/app/models/user.rb +2 -0
  132. data/spec/dummy/config/application.rb +26 -2
  133. data/spec/dummy/config/boot.rb +2 -4
  134. data/spec/dummy/config/environment.rb +1 -1
  135. data/spec/dummy/config/environments/development.rb +2 -0
  136. data/spec/dummy/config/environments/production.rb +2 -0
  137. data/spec/dummy/config/environments/test.rb +4 -2
  138. data/spec/dummy/config/initializers/backtrace_silencers.rb +2 -0
  139. data/spec/dummy/config/initializers/doorkeeper.rb +7 -3
  140. data/spec/dummy/config/initializers/secret_token.rb +3 -1
  141. data/spec/dummy/config/initializers/session_store.rb +3 -1
  142. data/spec/dummy/config/initializers/wrap_parameters.rb +2 -0
  143. data/spec/dummy/config.ru +3 -1
  144. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +20 -13
  145. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +1 -1
  146. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +2 -0
  147. data/spec/dummy/db/schema.rb +1 -1
  148. data/spec/dummy/script/rails +7 -4
  149. data/spec/factories.rb +11 -9
  150. data/spec/generators/application_owner_generator_spec.rb +13 -26
  151. data/spec/generators/confidential_applications_generator_spec.rb +12 -28
  152. data/spec/generators/install_generator_spec.rb +19 -14
  153. data/spec/generators/migration_generator_spec.rb +13 -26
  154. data/spec/generators/pkce_generator_spec.rb +11 -26
  155. data/spec/generators/previous_refresh_token_generator_spec.rb +16 -29
  156. data/spec/generators/templates/routes.rb +2 -1
  157. data/spec/generators/views_generator_spec.rb +14 -12
  158. data/spec/grape/grape_integration_spec.rb +35 -33
  159. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +9 -7
  160. data/spec/lib/config_spec.rb +290 -104
  161. data/spec/lib/doorkeeper_spec.rb +8 -6
  162. data/spec/lib/models/expirable_spec.rb +22 -8
  163. data/spec/lib/models/reusable_spec.rb +40 -0
  164. data/spec/lib/models/revocable_spec.rb +8 -6
  165. data/spec/lib/models/scopes_spec.rb +27 -13
  166. data/spec/lib/models/secret_storable_spec.rb +135 -0
  167. data/spec/lib/oauth/authorization/uri_builder_spec.rb +17 -15
  168. data/spec/lib/oauth/authorization_code_request_spec.rb +43 -17
  169. data/spec/lib/oauth/base_request_spec.rb +24 -12
  170. data/spec/lib/oauth/base_response_spec.rb +3 -1
  171. data/spec/lib/oauth/client/credentials_spec.rb +26 -24
  172. data/spec/lib/oauth/client_credentials/creator_spec.rb +56 -10
  173. data/spec/lib/oauth/client_credentials/issuer_spec.rb +27 -20
  174. data/spec/lib/oauth/client_credentials/validation_spec.rb +21 -15
  175. data/spec/lib/oauth/client_credentials_integration_spec.rb +8 -6
  176. data/spec/lib/oauth/client_credentials_request_spec.rb +27 -21
  177. data/spec/lib/oauth/client_spec.rb +15 -13
  178. data/spec/lib/oauth/code_request_spec.rb +10 -8
  179. data/spec/lib/oauth/code_response_spec.rb +9 -7
  180. data/spec/lib/oauth/error_response_spec.rb +19 -11
  181. data/spec/lib/oauth/error_spec.rb +4 -2
  182. data/spec/lib/oauth/forbidden_token_response_spec.rb +7 -5
  183. data/spec/lib/oauth/helpers/scope_checker_spec.rb +68 -31
  184. data/spec/lib/oauth/helpers/unique_token_spec.rb +8 -6
  185. data/spec/lib/oauth/helpers/uri_checker_spec.rb +124 -93
  186. data/spec/lib/oauth/invalid_token_response_spec.rb +3 -1
  187. data/spec/lib/oauth/password_access_token_request_spec.rb +95 -34
  188. data/spec/lib/oauth/pre_authorization_spec.rb +80 -54
  189. data/spec/lib/oauth/refresh_token_request_spec.rb +46 -39
  190. data/spec/lib/oauth/scopes_spec.rb +63 -61
  191. data/spec/lib/oauth/token_request_spec.rb +80 -25
  192. data/spec/lib/oauth/token_response_spec.rb +40 -38
  193. data/spec/lib/oauth/token_spec.rb +64 -45
  194. data/spec/lib/request/strategy_spec.rb +3 -1
  195. data/spec/lib/secret_storing/base_spec.rb +60 -0
  196. data/spec/lib/secret_storing/bcrypt_spec.rb +49 -0
  197. data/spec/lib/secret_storing/plain_spec.rb +44 -0
  198. data/spec/lib/secret_storing/sha256_hash_spec.rb +48 -0
  199. data/spec/lib/server_spec.rb +20 -18
  200. data/spec/lib/{orm/active_record/stale_records_cleaner_spec.rb → stale_records_cleaner_spec.rb} +28 -18
  201. data/spec/models/doorkeeper/access_grant_spec.rb +80 -15
  202. data/spec/models/doorkeeper/access_token_spec.rb +212 -83
  203. data/spec/models/doorkeeper/application_spec.rb +146 -49
  204. data/spec/requests/applications/applications_request_spec.rb +100 -68
  205. data/spec/requests/applications/authorized_applications_spec.rb +20 -18
  206. data/spec/requests/endpoints/authorization_spec.rb +27 -25
  207. data/spec/requests/endpoints/token_spec.rb +38 -36
  208. data/spec/requests/flows/authorization_code_errors_spec.rb +27 -25
  209. data/spec/requests/flows/authorization_code_spec.rb +260 -158
  210. data/spec/requests/flows/client_credentials_spec.rb +54 -52
  211. data/spec/requests/flows/implicit_grant_errors_spec.rb +11 -9
  212. data/spec/requests/flows/implicit_grant_spec.rb +27 -25
  213. data/spec/requests/flows/password_spec.rb +64 -56
  214. data/spec/requests/flows/refresh_token_spec.rb +91 -32
  215. data/spec/requests/flows/revoke_token_spec.rb +49 -63
  216. data/spec/requests/flows/skip_authorization_spec.rb +23 -21
  217. data/spec/requests/protected_resources/metal_spec.rb +7 -5
  218. data/spec/requests/protected_resources/private_api_spec.rb +37 -35
  219. data/spec/routing/custom_controller_routes_spec.rb +71 -65
  220. data/spec/routing/default_routes_spec.rb +22 -20
  221. data/spec/routing/scoped_routes_spec.rb +20 -18
  222. data/spec/spec_helper.rb +15 -13
  223. data/spec/spec_helper_integration.rb +3 -1
  224. data/spec/support/dependencies/factory_bot.rb +3 -1
  225. data/spec/support/doorkeeper_rspec.rb +5 -2
  226. data/spec/support/helpers/access_token_request_helper.rb +3 -1
  227. data/spec/support/helpers/authorization_request_helper.rb +4 -2
  228. data/spec/support/helpers/config_helper.rb +2 -0
  229. data/spec/support/helpers/model_helper.rb +11 -5
  230. data/spec/support/helpers/request_spec_helper.rb +5 -3
  231. data/spec/support/helpers/url_helper.rb +20 -18
  232. data/spec/support/http_method_shim.rb +4 -9
  233. data/spec/support/orm/active_record.rb +3 -1
  234. data/spec/support/shared/controllers_shared_context.rb +68 -10
  235. data/spec/support/shared/hashing_shared_context.rb +36 -0
  236. data/spec/support/shared/models_shared_examples.rb +12 -10
  237. data/spec/validators/redirect_uri_validator_spec.rb +76 -47
  238. data/spec/version/version_spec.rb +7 -5
  239. metadata +70 -21
  240. data/gemfiles/rails_4_2.gemfile +0 -13
  241. data/spec/dummy/config/initializers/new_framework_defaults.rb +0 -10
@@ -1,26 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module AccessTokenMixin
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  include OAuth::Helpers
6
8
  include Models::Expirable
9
+ include Models::Reusable
7
10
  include Models::Revocable
8
11
  include Models::Accessible
9
12
  include Models::Orderable
13
+ include Models::SecretStorable
10
14
  include Models::Scopes
11
15
 
12
16
  module ClassMethods
13
17
  # Returns an instance of the Doorkeeper::AccessToken with
14
- # specific token value.
18
+ # specific plain text token value.
15
19
  #
16
20
  # @param token [#to_s]
17
- # token value (any object that responds to `#to_s`)
21
+ # Plain text token value (any object that responds to `#to_s`)
18
22
  #
19
23
  # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
20
24
  # if there is no record with such token
21
25
  #
22
26
  def by_token(token)
23
- find_by(token: token.to_s)
27
+ find_by_plaintext_token(:token, token)
24
28
  end
25
29
 
26
30
  # Returns an instance of the Doorkeeper::AccessToken
@@ -33,7 +37,7 @@ module Doorkeeper
33
37
  # if there is no record with such refresh token
34
38
  #
35
39
  def by_refresh_token(refresh_token)
36
- find_by(refresh_token: refresh_token.to_s)
40
+ find_by_plaintext_token(:refresh_token, refresh_token)
37
41
  end
38
42
 
39
43
  # Revokes AccessToken records that have not been revoked and associated
@@ -47,11 +51,11 @@ module Doorkeeper
47
51
  def revoke_all_for(application_id, resource_owner, clock = Time)
48
52
  where(application_id: application_id,
49
53
  resource_owner_id: resource_owner.id,
50
- revoked_at: nil).
51
- update_all(revoked_at: clock.now.utc)
54
+ revoked_at: nil)
55
+ .update_all(revoked_at: clock.now.utc)
52
56
  end
53
57
 
54
- # Looking for not expired Access Token with a matching set of scopes
58
+ # Looking for not revoked Access Token with a matching set of scopes
55
59
  # that belongs to specific Application and Resource Owner.
56
60
  #
57
61
  # @param application [Doorkeeper::Application]
@@ -96,9 +100,9 @@ module Doorkeeper
96
100
 
97
101
  (token_scopes.sort == param_scopes.sort) &&
98
102
  Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
99
- param_scopes.to_s,
100
- Doorkeeper.configuration.scopes,
101
- app_scopes
103
+ scope_str: param_scopes.to_s,
104
+ server_scopes: Doorkeeper.configuration.scopes,
105
+ app_scopes: app_scopes
102
106
  )
103
107
  end
104
108
 
@@ -123,14 +127,14 @@ module Doorkeeper
123
127
  if Doorkeeper.configuration.reuse_access_token
124
128
  access_token = matching_token_for(application, resource_owner_id, scopes)
125
129
 
126
- return access_token if access_token && !access_token.expired?
130
+ return access_token if access_token&.reusable?
127
131
  end
128
132
 
129
133
  create!(
130
- application_id: application.try(:id),
134
+ application_id: application.try(:id),
131
135
  resource_owner_id: resource_owner_id,
132
- scopes: scopes.to_s,
133
- expires_in: expires_in,
136
+ scopes: scopes.to_s,
137
+ expires_in: expires_in,
134
138
  use_refresh_token: use_refresh_token
135
139
  )
136
140
  end
@@ -166,6 +170,20 @@ module Doorkeeper
166
170
  def last_authorized_token_for(application_id, resource_owner_id)
167
171
  authorized_tokens_for(application_id, resource_owner_id).first
168
172
  end
173
+
174
+ ##
175
+ # Determines the secret storing transformer
176
+ # Unless configured otherwise, uses the plain secret strategy
177
+ def secret_strategy
178
+ ::Doorkeeper.configuration.token_secret_strategy
179
+ end
180
+
181
+ ##
182
+ # Determine the fallback storing strategy
183
+ # Unless configured, there will be no fallback
184
+ def fallback_secret_strategy
185
+ ::Doorkeeper.configuration.token_secret_fallback_strategy
186
+ end
169
187
  end
170
188
 
171
189
  # Access Token type: Bearer.
@@ -173,7 +191,7 @@ module Doorkeeper
173
191
  # The OAuth 2.0 Authorization Framework: Bearer Token Usage
174
192
  #
175
193
  def token_type
176
- 'Bearer'
194
+ "Bearer"
177
195
  end
178
196
 
179
197
  def use_refresh_token?
@@ -186,11 +204,11 @@ module Doorkeeper
186
204
  # @return [Hash] hash with token data
187
205
  def as_json(_options = {})
188
206
  {
189
- resource_owner_id: resource_owner_id,
190
- scope: scopes,
191
- expires_in: expires_in_seconds,
192
- application: { uid: application.try(:uid) },
193
- created_at: created_at.to_i
207
+ resource_owner_id: resource_owner_id,
208
+ scope: scopes,
209
+ expires_in: expires_in_seconds,
210
+ application: { uid: application.try(:uid) },
211
+ created_at: created_at.to_i,
194
212
  }
195
213
  end
196
214
 
@@ -217,6 +235,30 @@ module Doorkeeper
217
235
  accessible? && includes_scope?(*scopes)
218
236
  end
219
237
 
238
+ # We keep a volatile copy of the raw refresh token for initial communication
239
+ # The stored refresh_token may be mapped and not available in cleartext.
240
+ def plaintext_refresh_token
241
+ if secret_strategy.allows_restoring_secrets?
242
+ secret_strategy.restore_secret(self, :refresh_token)
243
+ else
244
+ @raw_refresh_token
245
+ end
246
+ end
247
+
248
+ # We keep a volatile copy of the raw token for initial communication
249
+ # The stored refresh_token may be mapped and not available in cleartext.
250
+ #
251
+ # Some strategies allow restoring stored secrets (e.g. symmetric encryption)
252
+ # while hashing strategies do not, so you cannot rely on this value
253
+ # returning a present value for persisted tokens.
254
+ def plaintext_token
255
+ if secret_strategy.allows_restoring_secrets?
256
+ secret_strategy.restore_secret(self, :token)
257
+ else
258
+ @raw_token
259
+ end
260
+ end
261
+
220
262
  private
221
263
 
222
264
  # Generates refresh token with UniqueToken generator.
@@ -224,7 +266,8 @@ module Doorkeeper
224
266
  # @return [String] refresh token value
225
267
  #
226
268
  def generate_refresh_token
227
- self.refresh_token = UniqueToken.generate
269
+ @raw_refresh_token = UniqueToken.generate
270
+ secret_strategy.store_secret(self, :refresh_token, @raw_refresh_token)
228
271
  end
229
272
 
230
273
  # Generates and sets the token value with the
@@ -240,13 +283,16 @@ module Doorkeeper
240
283
  def generate_token
241
284
  self.created_at ||= Time.now.utc
242
285
 
243
- self.token = token_generator.generate(
286
+ @raw_token = token_generator.generate(
244
287
  resource_owner_id: resource_owner_id,
245
288
  scopes: scopes,
246
289
  application: application,
247
290
  expires_in: expires_in,
248
291
  created_at: created_at
249
292
  )
293
+
294
+ secret_strategy.store_secret(self, :token, @raw_token)
295
+ @raw_token
250
296
  end
251
297
 
252
298
  def token_generator
@@ -1,11 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module ApplicationMixin
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  include OAuth::Helpers
6
8
  include Models::Orderable
9
+ include Models::SecretStorable
7
10
  include Models::Scopes
8
11
 
12
+ # :nodoc
9
13
  module ClassMethods
10
14
  # Returns an instance of the Doorkeeper::Application with
11
15
  # specific UID and secret.
@@ -23,7 +27,8 @@ module Doorkeeper
23
27
  app = by_uid(uid)
24
28
  return unless app
25
29
  return app if secret.blank? && !app.confidential?
26
- return unless app.secret == secret
30
+ return unless app.secret_matches?(secret)
31
+
27
32
  app
28
33
  end
29
34
 
@@ -37,6 +42,20 @@ module Doorkeeper
37
42
  def by_uid(uid)
38
43
  find_by(uid: uid.to_s)
39
44
  end
45
+
46
+ ##
47
+ # Determines the secret storing transformer
48
+ # Unless configured otherwise, uses the plain secret strategy
49
+ def secret_strategy
50
+ ::Doorkeeper.configuration.application_secret_strategy
51
+ end
52
+
53
+ ##
54
+ # Determine the fallback storing strategy
55
+ # Unless configured, there will be no fallback
56
+ def fallback_secret_strategy
57
+ ::Doorkeeper.configuration.application_secret_fallback_strategy
58
+ end
40
59
  end
41
60
 
42
61
  # Set an application's valid redirect URIs.
@@ -47,5 +66,30 @@ module Doorkeeper
47
66
  def redirect_uri=(uris)
48
67
  super(uris.is_a?(Array) ? uris.join("\n") : uris)
49
68
  end
69
+
70
+ # Check whether the given plain text secret matches our stored secret
71
+ #
72
+ # @param input [#to_s] Plain secret provided by user
73
+ # (any object that responds to `#to_s`)
74
+ #
75
+ # @return [true] Whether the given secret matches the stored secret
76
+ # of this application.
77
+ #
78
+ def secret_matches?(input)
79
+ # return false if either is nil, since secure_compare depends on strings
80
+ # but Application secrets MAY be nil depending on confidentiality.
81
+ return false if input.nil? || secret.nil?
82
+
83
+ # When matching the secret by comparer function, all is well.
84
+ return true if secret_strategy.secret_matches?(input, secret)
85
+
86
+ # When fallback lookup is enabled, ensure applications
87
+ # with plain secrets can still be found
88
+ if fallback_secret_strategy
89
+ fallback_secret_strategy.secret_matches?(input, secret)
90
+ else
91
+ false
92
+ end
93
+ end
50
94
  end
51
95
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Accessible
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Expirable
@@ -15,6 +17,7 @@ module Doorkeeper
15
17
  # or nil if object never expires.
16
18
  def expires_in_seconds
17
19
  return nil if expires_in.nil?
20
+
18
21
  expires = expires_at - Time.now.utc
19
22
  expires_sec = expires.seconds.round(0)
20
23
  expires_sec > 0 ? expires_sec : 0
@@ -22,10 +25,11 @@ module Doorkeeper
22
25
 
23
26
  # Expiration time (date time of creation + TTL).
24
27
  #
25
- # @return [Time] expiration time in UTC
28
+ # @return [Time, nil] expiration time in UTC
29
+ # or nil if the object never expires.
26
30
  #
27
31
  def expires_at
28
- created_at + expires_in.seconds
32
+ expires_in && created_at + expires_in.seconds
29
33
  end
30
34
  end
31
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Orderable
@@ -1,15 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Ownership
4
6
  extend ActiveSupport::Concern
5
7
 
6
8
  included do
7
- belongs_to_options = { polymorphic: true }
8
- if defined?(ActiveRecord::Base) && ActiveRecord::VERSION::MAJOR >= 5
9
- belongs_to_options[:optional] = true
10
- end
11
-
12
- belongs_to :owner, belongs_to_options
9
+ belongs_to :owner, polymorphic: true, optional: true
13
10
  validates :owner, presence: true, if: :validate_owner?
14
11
  end
15
12
 
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module Models
5
+ module Reusable
6
+ # Indicates whether the object is reusable (i.e. It is not expired and
7
+ # has not crossed reuse_limit).
8
+ #
9
+ # @return [Boolean] true if can be reused and false in other case
10
+ def reusable?
11
+ return false if expired?
12
+ return true unless expires_in
13
+
14
+ threshold_limit = 100 - Doorkeeper.configuration.token_reuse_limit
15
+ expires_in_seconds >= threshold_limit * expires_in / 100
16
+ end
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Revocable
@@ -23,7 +25,8 @@ module Doorkeeper
23
25
  #
24
26
  def revoke_previous_refresh_token!
25
27
  return unless refresh_token_revoked_on_use?
26
- old_refresh_token.revoke if old_refresh_token
28
+
29
+ old_refresh_token&.revoke
27
30
  update_attribute :previous_refresh_token, ""
28
31
  end
29
32
 
@@ -1,8 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module Models
3
5
  module Scopes
4
6
  def scopes
5
- OAuth::Scopes.from_string(self[:scopes])
7
+ OAuth::Scopes.from_string(scopes_string)
8
+ end
9
+
10
+ def scopes=(value)
11
+ super Array(value).join(" ")
6
12
  end
7
13
 
8
14
  def scopes_string
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doorkeeper
4
+ module Models
5
+ ##
6
+ # Storable finder to provide lookups for input plaintext values which are
7
+ # mapped to their stored versions (e.g., hashing, encryption) before lookup.
8
+ module SecretStorable
9
+ extend ActiveSupport::Concern
10
+
11
+ delegate :secret_strategy,
12
+ :fallback_secret_strategy,
13
+ to: :class
14
+
15
+ # :nodoc
16
+ module ClassMethods
17
+ # Compare the given plaintext with the secret
18
+ #
19
+ # @param input [String]
20
+ # The plain input to compare.
21
+ #
22
+ # @param secret [String]
23
+ # The secret value to compare with.
24
+ #
25
+ # @return [Boolean]
26
+ # Whether input matches secret as per the secret strategy
27
+ #
28
+ def secret_matches?(input, secret)
29
+ secret_strategy.secret_matches?(input, secret)
30
+ end
31
+
32
+ # Returns an instance of the Doorkeeper::AccessToken with
33
+ # specific token value.
34
+ #
35
+ # @param attr [Symbol]
36
+ # The token attribute we're looking with.
37
+ #
38
+ # @param token [#to_s]
39
+ # token value (any object that responds to `#to_s`)
40
+ #
41
+ # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
42
+ # if there is no record with such token
43
+ #
44
+ def find_by_plaintext_token(attr, token)
45
+ token = token.to_s
46
+
47
+ find_by(attr => secret_strategy.transform_secret(token)) ||
48
+ find_by_fallback_token(attr, token)
49
+ end
50
+
51
+ # Allow looking up previously plain tokens as a fallback
52
+ # IFF a fallback strategy has been defined
53
+ #
54
+ # @param attr [Symbol]
55
+ # The token attribute we're looking with.
56
+ #
57
+ # @param plain_secret [#to_s]
58
+ # plain secret value (any object that responds to `#to_s`)
59
+ #
60
+ # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
61
+ # if there is no record with such token
62
+ #
63
+ def find_by_fallback_token(attr, plain_secret)
64
+ return nil unless fallback_secret_strategy
65
+
66
+ # Use the previous strategy to look up
67
+ stored_token = fallback_secret_strategy.transform_secret(plain_secret)
68
+ find_by(attr => stored_token).tap do |resource|
69
+ return nil unless resource
70
+
71
+ upgrade_fallback_value resource, attr, plain_secret
72
+ end
73
+ end
74
+
75
+ # Allow implementations in ORMs to replace a plain
76
+ # value falling back to to avoid it remaining as plain text.
77
+ #
78
+ # @param instance
79
+ # An instance of this model with a plain value token.
80
+ #
81
+ # @param attr
82
+ # The secret attribute name to upgrade.
83
+ #
84
+ # @param plain_secret
85
+ # The plain secret to upgrade.
86
+ #
87
+ def upgrade_fallback_value(instance, attr, plain_secret)
88
+ upgraded = secret_strategy.store_secret(instance, attr, plain_secret)
89
+ instance.update(attr => upgraded)
90
+ end
91
+
92
+ ##
93
+ # Determines the secret storing transformer
94
+ # Unless configured otherwise, uses the plain secret strategy
95
+ def secret_strategy
96
+ ::Doorkeeper::SecretStoring::Plain
97
+ end
98
+
99
+ ##
100
+ # Determine the fallback storing strategy
101
+ # Unless configured, there will be no fallback
102
+ def fallback_secret_strategy
103
+ nil
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -14,7 +16,7 @@ module Doorkeeper
14
16
  end
15
17
 
16
18
  def native_redirect
17
- { action: :show, code: token.token }
19
+ { action: :show, code: token.plaintext_token }
18
20
  end
19
21
 
20
22
  def configuration
@@ -40,7 +42,7 @@ module Doorkeeper
40
42
 
41
43
  {
42
44
  code_challenge: pre_auth.code_challenge,
43
- code_challenge_method: pre_auth.code_challenge_method
45
+ code_challenge_method: pre_auth.code_challenge_method,
44
46
  }
45
47
  end
46
48
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  module Authorization
@@ -6,7 +8,9 @@ module Doorkeeper
6
8
 
7
9
  class << self
8
10
  def build_context(pre_auth_or_oauth_client, grant_type, scopes)
9
- oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
11
+ oauth_client = if pre_auth_or_oauth_client.respond_to?(:application)
12
+ pre_auth_or_oauth_client.application
13
+ elsif pre_auth_or_oauth_client.respond_to?(:client)
10
14
  pre_auth_or_oauth_client.client
11
15
  else
12
16
  pre_auth_or_oauth_client
@@ -19,11 +23,14 @@ module Doorkeeper
19
23
  )
20
24
  end
21
25
 
22
- def access_token_expires_in(server, context)
23
- if (expiration = server.custom_access_token_expires_in.call(context))
24
- expiration
26
+ def access_token_expires_in(configuration, context)
27
+ if configuration.option_defined?(:custom_access_token_expires_in)
28
+ expiration = configuration.custom_access_token_expires_in.call(context)
29
+ return nil if expiration == Float::INFINITY
30
+
31
+ expiration || configuration.access_token_expires_in
25
32
  else
26
- server.access_token_expires_in
33
+ configuration.access_token_expires_in
27
34
  end
28
35
  end
29
36
 
@@ -60,7 +67,7 @@ module Doorkeeper
60
67
  {
61
68
  controller: controller,
62
69
  action: :show,
63
- access_token: token.token
70
+ access_token: token.plaintext_token,
64
71
  }
65
72
  end
66
73
 
@@ -73,7 +80,7 @@ module Doorkeeper
73
80
  def controller
74
81
  @controller ||= begin
75
82
  mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
76
- mapping[:controllers] || 'doorkeeper/token_info'
83
+ mapping[:controllers] || "doorkeeper/token_info"
77
84
  end
78
85
  end
79
86
  end
@@ -1,4 +1,6 @@
1
- require 'rack/utils'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/utils"
2
4
 
3
5
  module Doorkeeper
4
6
  module OAuth
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class AuthorizationCodeRequest < BaseRequest
@@ -41,17 +43,19 @@ module Doorkeeper
41
43
  end
42
44
 
43
45
  def validate_attributes
44
- return false if grant && grant.uses_pkce? && code_verifier.blank?
46
+ return false if grant&.uses_pkce? && code_verifier.blank?
45
47
  return false if grant && !grant.pkce_supported? && !code_verifier.blank?
48
+
46
49
  redirect_uri.present?
47
50
  end
48
51
 
49
52
  def validate_client
50
- !client.nil?
53
+ client.present?
51
54
  end
52
55
 
53
56
  def validate_grant
54
57
  return false unless grant && grant.application_id == client.id
58
+
55
59
  grant.accessible?
56
60
  end
57
61
 
@@ -68,9 +72,9 @@ module Doorkeeper
68
72
  return true unless grant.uses_pkce? || code_verifier
69
73
  return false unless grant.pkce_supported?
70
74
 
71
- if grant.code_challenge_method == 'S256'
75
+ if grant.code_challenge_method == "S256"
72
76
  grant.code_challenge == AccessGrant.generate_code_challenge(code_verifier)
73
- elsif grant.code_challenge_method == 'plain'
77
+ elsif grant.code_challenge_method == "plain"
74
78
  grant.code_challenge == code_verifier
75
79
  else
76
80
  false
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class BaseResponse
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Client
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class Client
@@ -16,7 +18,7 @@ module Doorkeeper
16
18
  end
17
19
 
18
20
  def self.authenticate(credentials, method = Application.method(:by_uid_and_secret))
19
- return false if credentials.blank?
21
+ return if credentials.blank?
20
22
 
21
23
  if (application = method.call(credentials.uid, credentials.secret))
22
24
  new(application)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  module OAuth
3
5
  class ClientCredentialsRequest < BaseRequest