doorkeeper 4.3.2 → 5.0.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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.gitlab-ci.yml +16 -0
  4. data/.rubocop.yml +4 -0
  5. data/.travis.yml +2 -0
  6. data/Appraisals +2 -2
  7. data/Gemfile +1 -1
  8. data/NEWS.md +70 -0
  9. data/README.md +92 -9
  10. data/Rakefile +6 -0
  11. data/UPGRADE.md +2 -0
  12. data/app/assets/stylesheets/doorkeeper/admin/application.css +2 -2
  13. data/app/controllers/doorkeeper/application_controller.rb +4 -3
  14. data/app/controllers/doorkeeper/application_metal_controller.rb +4 -0
  15. data/app/controllers/doorkeeper/applications_controller.rb +43 -22
  16. data/app/controllers/doorkeeper/authorizations_controller.rb +55 -12
  17. data/app/controllers/doorkeeper/authorized_applications_controller.rb +19 -2
  18. data/app/controllers/doorkeeper/tokens_controller.rb +11 -16
  19. data/app/helpers/doorkeeper/dashboard_helper.rb +7 -7
  20. data/app/validators/redirect_uri_validator.rb +3 -2
  21. data/app/views/doorkeeper/applications/_delete_form.html.erb +3 -1
  22. data/app/views/doorkeeper/applications/_form.html.erb +31 -19
  23. data/app/views/doorkeeper/applications/edit.html.erb +1 -1
  24. data/app/views/doorkeeper/applications/index.html.erb +18 -6
  25. data/app/views/doorkeeper/applications/new.html.erb +1 -1
  26. data/app/views/doorkeeper/applications/show.html.erb +8 -5
  27. data/app/views/doorkeeper/authorizations/error.html.erb +1 -1
  28. data/app/views/doorkeeper/authorizations/new.html.erb +4 -0
  29. data/app/views/layouts/doorkeeper/admin.html.erb +15 -15
  30. data/config/locales/en.yml +16 -1
  31. data/doorkeeper.gemspec +18 -18
  32. data/gemfiles/rails_5_2.gemfile +1 -1
  33. data/gemfiles/rails_master.gemfile +4 -1
  34. data/lib/doorkeeper/config.rb +77 -27
  35. data/lib/doorkeeper/engine.rb +4 -0
  36. data/lib/doorkeeper/errors.rb +2 -5
  37. data/lib/doorkeeper/grape/helpers.rb +1 -1
  38. data/lib/doorkeeper/helpers/controller.rb +7 -2
  39. data/lib/doorkeeper/models/access_grant_mixin.rb +71 -0
  40. data/lib/doorkeeper/models/access_token_mixin.rb +40 -23
  41. data/lib/doorkeeper/models/application_mixin.rb +8 -1
  42. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  43. data/lib/doorkeeper/oauth/authorization/code.rb +31 -8
  44. data/lib/doorkeeper/oauth/authorization/context.rb +15 -0
  45. data/lib/doorkeeper/oauth/authorization/token.rb +36 -14
  46. data/lib/doorkeeper/oauth/authorization_code_request.rb +27 -2
  47. data/lib/doorkeeper/oauth/base_request.rb +20 -9
  48. data/lib/doorkeeper/oauth/client/credentials.rb +4 -2
  49. data/lib/doorkeeper/oauth/client.rb +0 -2
  50. data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -1
  51. data/lib/doorkeeper/oauth/client_credentials/issuer.rb +6 -3
  52. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -6
  53. data/lib/doorkeeper/oauth/client_credentials_request.rb +0 -4
  54. data/lib/doorkeeper/oauth/error_response.rb +11 -3
  55. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +0 -8
  56. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +1 -0
  57. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -4
  58. data/lib/doorkeeper/oauth/pre_authorization.rb +45 -13
  59. data/lib/doorkeeper/oauth/refresh_token_request.rb +6 -1
  60. data/lib/doorkeeper/oauth/scopes.rb +1 -1
  61. data/lib/doorkeeper/oauth/token.rb +5 -2
  62. data/lib/doorkeeper/oauth/token_introspection.rb +2 -2
  63. data/lib/doorkeeper/oauth/token_response.rb +4 -2
  64. data/lib/doorkeeper/oauth.rb +13 -0
  65. data/lib/doorkeeper/orm/active_record/application.rb +25 -0
  66. data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +26 -0
  67. data/lib/doorkeeper/orm/active_record.rb +2 -0
  68. data/lib/doorkeeper/rails/helpers.rb +2 -4
  69. data/lib/doorkeeper/rails/routes.rb +13 -1
  70. data/lib/doorkeeper/rake/db.rake +40 -0
  71. data/lib/doorkeeper/rake/setup.rake +6 -0
  72. data/lib/doorkeeper/rake.rb +14 -0
  73. data/lib/doorkeeper/request/authorization_code.rb +0 -2
  74. data/lib/doorkeeper/request/client_credentials.rb +0 -2
  75. data/lib/doorkeeper/request/code.rb +0 -2
  76. data/lib/doorkeeper/request/password.rb +1 -13
  77. data/lib/doorkeeper/request/refresh_token.rb +0 -2
  78. data/lib/doorkeeper/request/token.rb +0 -2
  79. data/lib/doorkeeper/request.rb +28 -35
  80. data/lib/doorkeeper/version.rb +5 -4
  81. data/lib/doorkeeper.rb +19 -17
  82. data/lib/generators/doorkeeper/application_owner_generator.rb +23 -18
  83. data/lib/generators/doorkeeper/confidential_applications_generator.rb +32 -0
  84. data/lib/generators/doorkeeper/install_generator.rb +17 -9
  85. data/lib/generators/doorkeeper/migration_generator.rb +23 -18
  86. data/lib/generators/doorkeeper/pkce_generator.rb +32 -0
  87. data/lib/generators/doorkeeper/previous_refresh_token_generator.rb +29 -24
  88. data/lib/generators/doorkeeper/templates/add_confidential_to_applications.rb.erb +13 -0
  89. data/lib/generators/doorkeeper/templates/enable_pkce_migration.rb.erb +6 -0
  90. data/lib/generators/doorkeeper/templates/initializer.rb +76 -11
  91. data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -0
  92. data/lib/generators/doorkeeper/views_generator.rb +3 -1
  93. data/spec/controllers/application_metal_controller_spec.rb +50 -0
  94. data/spec/controllers/applications_controller_spec.rb +126 -13
  95. data/spec/controllers/authorizations_controller_spec.rb +279 -17
  96. data/spec/controllers/protected_resources_controller_spec.rb +16 -16
  97. data/spec/controllers/token_info_controller_spec.rb +4 -12
  98. data/spec/controllers/tokens_controller_spec.rb +72 -22
  99. data/spec/dummy/app/assets/config/manifest.js +2 -0
  100. data/spec/dummy/config/environments/test.rb +4 -5
  101. data/spec/dummy/config/initializers/doorkeeper.rb +11 -1
  102. data/spec/dummy/config/initializers/new_framework_defaults.rb +4 -0
  103. data/spec/dummy/config/routes.rb +3 -42
  104. data/spec/dummy/db/migrate/20111122132257_create_users.rb +3 -1
  105. data/spec/dummy/db/migrate/20120312140401_add_password_to_users.rb +3 -1
  106. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +3 -1
  107. data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +3 -1
  108. data/spec/dummy/db/migrate/20160320211015_add_previous_refresh_token_to_access_tokens.rb +3 -1
  109. data/spec/dummy/db/migrate/20170822064514_enable_pkce.rb +6 -0
  110. data/spec/dummy/db/migrate/20180210183654_add_confidential_to_applications.rb +13 -0
  111. data/spec/dummy/db/schema.rb +38 -37
  112. data/spec/generators/application_owner_generator_spec.rb +1 -1
  113. data/spec/generators/confidential_applications_generator_spec.rb +45 -0
  114. data/spec/generators/install_generator_spec.rb +1 -1
  115. data/spec/generators/migration_generator_spec.rb +1 -1
  116. data/spec/generators/pkce_generator_spec.rb +43 -0
  117. data/spec/generators/previous_refresh_token_generator_spec.rb +1 -1
  118. data/spec/generators/views_generator_spec.rb +1 -1
  119. data/spec/grape/grape_integration_spec.rb +1 -1
  120. data/spec/helpers/doorkeeper/dashboard_helper_spec.rb +1 -1
  121. data/spec/lib/config_spec.rb +88 -14
  122. data/spec/lib/doorkeeper_spec.rb +1 -126
  123. data/spec/lib/models/expirable_spec.rb +0 -3
  124. data/spec/lib/models/revocable_spec.rb +0 -2
  125. data/spec/lib/models/scopes_spec.rb +0 -4
  126. data/spec/lib/oauth/authorization/uri_builder_spec.rb +0 -4
  127. data/spec/lib/oauth/authorization_code_request_spec.rb +24 -2
  128. data/spec/lib/oauth/base_request_spec.rb +40 -2
  129. data/spec/lib/oauth/base_response_spec.rb +1 -1
  130. data/spec/lib/oauth/client/credentials_spec.rb +5 -5
  131. data/spec/lib/oauth/client_credentials/creator_spec.rb +5 -1
  132. data/spec/lib/oauth/client_credentials/issuer_spec.rb +26 -7
  133. data/spec/lib/oauth/client_credentials/validation_spec.rb +2 -3
  134. data/spec/lib/oauth/client_credentials_integration_spec.rb +1 -1
  135. data/spec/lib/oauth/client_credentials_request_spec.rb +3 -5
  136. data/spec/lib/oauth/client_spec.rb +0 -3
  137. data/spec/lib/oauth/code_request_spec.rb +4 -2
  138. data/spec/lib/oauth/error_response_spec.rb +0 -3
  139. data/spec/lib/oauth/error_spec.rb +0 -2
  140. data/spec/lib/oauth/forbidden_token_response_spec.rb +1 -4
  141. data/spec/lib/oauth/helpers/scope_checker_spec.rb +0 -3
  142. data/spec/lib/oauth/helpers/unique_token_spec.rb +0 -1
  143. data/spec/lib/oauth/helpers/uri_checker_spec.rb +5 -2
  144. data/spec/lib/oauth/invalid_token_response_spec.rb +1 -4
  145. data/spec/lib/oauth/password_access_token_request_spec.rb +37 -2
  146. data/spec/lib/oauth/pre_authorization_spec.rb +40 -6
  147. data/spec/lib/oauth/refresh_token_request_spec.rb +11 -7
  148. data/spec/lib/oauth/scopes_spec.rb +0 -3
  149. data/spec/lib/oauth/token_request_spec.rb +4 -5
  150. data/spec/lib/oauth/token_response_spec.rb +0 -1
  151. data/spec/lib/oauth/token_spec.rb +37 -14
  152. data/spec/lib/orm/active_record/stale_records_cleaner_spec.rb +79 -0
  153. data/spec/lib/request/strategy_spec.rb +0 -1
  154. data/spec/lib/server_spec.rb +1 -1
  155. data/spec/models/doorkeeper/access_grant_spec.rb +44 -1
  156. data/spec/models/doorkeeper/access_token_spec.rb +66 -22
  157. data/spec/models/doorkeeper/application_spec.rb +64 -6
  158. data/spec/requests/applications/applications_request_spec.rb +134 -1
  159. data/spec/requests/applications/authorized_applications_spec.rb +1 -1
  160. data/spec/requests/endpoints/authorization_spec.rb +1 -1
  161. data/spec/requests/endpoints/token_spec.rb +7 -5
  162. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -1
  163. data/spec/requests/flows/authorization_code_spec.rb +198 -2
  164. data/spec/requests/flows/client_credentials_spec.rb +46 -6
  165. data/spec/requests/flows/implicit_grant_errors_spec.rb +1 -1
  166. data/spec/requests/flows/implicit_grant_spec.rb +38 -11
  167. data/spec/requests/flows/password_spec.rb +120 -23
  168. data/spec/requests/flows/refresh_token_spec.rb +2 -2
  169. data/spec/requests/flows/revoke_token_spec.rb +11 -11
  170. data/spec/requests/flows/skip_authorization_spec.rb +16 -11
  171. data/spec/requests/protected_resources/metal_spec.rb +1 -1
  172. data/spec/requests/protected_resources/private_api_spec.rb +1 -1
  173. data/spec/routing/custom_controller_routes_spec.rb +59 -7
  174. data/spec/routing/default_routes_spec.rb +2 -2
  175. data/spec/routing/scoped_routes_spec.rb +16 -2
  176. data/spec/spec_helper.rb +54 -3
  177. data/spec/spec_helper_integration.rb +2 -74
  178. data/spec/support/doorkeeper_rspec.rb +19 -0
  179. data/spec/support/helpers/authorization_request_helper.rb +4 -4
  180. data/spec/support/helpers/request_spec_helper.rb +10 -2
  181. data/spec/support/helpers/url_helper.rb +7 -3
  182. data/spec/support/http_method_shim.rb +12 -16
  183. data/spec/validators/redirect_uri_validator_spec.rb +7 -1
  184. data/spec/version/version_spec.rb +3 -3
  185. data/vendor/assets/stylesheets/doorkeeper/bootstrap.min.css +4 -5
  186. metadata +36 -10
  187. data/spec/controllers/application_metal_controller.rb +0 -10
  188. /data/spec/support/dependencies/{factory_girl.rb → factory_bot.rb} +0 -0
@@ -15,19 +15,19 @@ module Doorkeeper
15
15
  end
16
16
 
17
17
  def self.configuration
18
- @config || (fail MissingConfiguration)
18
+ @config || (raise MissingConfiguration)
19
19
  end
20
20
 
21
21
  def self.setup_orm_adapter
22
22
  @orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize
23
- rescue NameError => e
24
- fail e, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.squish
25
- [doorkeeper] ORM adapter not found (#{configuration.orm}), or there was an error
26
- trying to load it.
27
-
28
- You probably need to add the related gem for this adapter to work with
29
- doorkeeper.
30
- ERROR_MSG
23
+ rescue NameError => error
24
+ raise error, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.strip_heredoc
25
+ [doorkeeper] ORM adapter not found (#{configuration.orm}), or there was an error
26
+ trying to load it.
27
+
28
+ You probably need to add the related gem for this adapter to work with
29
+ doorkeeper.
30
+ ERROR_MSG
31
31
  end
32
32
 
33
33
  def self.setup_orm_models
@@ -90,7 +90,7 @@ doorkeeper.
90
90
  #
91
91
  # @param methods [Array] Define client credentials
92
92
  def client_credentials(*methods)
93
- @config.instance_variable_set(:@client_credentials, methods)
93
+ @config.instance_variable_set(:@client_credentials_methods, methods)
94
94
  end
95
95
 
96
96
  # Change the way access token is authenticated from the request object.
@@ -103,9 +103,12 @@ doorkeeper.
103
103
  @config.instance_variable_set(:@access_token_methods, methods)
104
104
  end
105
105
 
106
- # Issue access tokens with refresh token (disabled by default)
107
- def use_refresh_token
108
- @config.instance_variable_set(:@refresh_token_enabled, true)
106
+ # Issue access tokens with refresh token (disabled if not set)
107
+ def use_refresh_token(enabled = true, &block)
108
+ @config.instance_variable_set(
109
+ :@refresh_token_enabled,
110
+ block ? block : enabled
111
+ )
109
112
  end
110
113
 
111
114
  # Reuse access token for the same resource owner within an application
@@ -114,6 +117,25 @@ doorkeeper.
114
117
  def reuse_access_token
115
118
  @config.instance_variable_set(:@reuse_access_token, true)
116
119
  end
120
+
121
+ # Use an API mode for applications generated with --api argument
122
+ # It will skip applications controller, disable forgery protection
123
+ def api_only
124
+ @config.instance_variable_set(:@api_only, true)
125
+ end
126
+
127
+ # Forbids creating/updating applications with arbitrary scopes that are
128
+ # not in configuration, i.e. `default_scopes` or `optional_scopes`.
129
+ # (disabled by default)
130
+ def enforce_configured_scopes
131
+ @config.instance_variable_set(:@enforce_configured_scopes, true)
132
+ end
133
+
134
+ # Enforce request content type as the spec requires:
135
+ # disabled by default for backward compatibility.
136
+ def enforce_content_type
137
+ @config.instance_variable_set(:@enforce_content_type, true)
138
+ end
117
139
  end
118
140
 
119
141
  module Option
@@ -179,25 +201,39 @@ doorkeeper.
179
201
  option :resource_owner_authenticator,
180
202
  as: :authenticate_resource_owner,
181
203
  default: (lambda do |_routes|
182
- ::Rails.logger.warn(I18n.t('doorkeeper.errors.messages.resource_owner_authenticator_not_configured'))
204
+ ::Rails.logger.warn(
205
+ I18n.t('doorkeeper.errors.messages.resource_owner_authenticator_not_configured')
206
+ )
207
+
183
208
  nil
184
209
  end)
185
210
 
186
211
  option :admin_authenticator,
187
212
  as: :authenticate_admin,
188
- default: ->(_routes) {}
213
+ default: (lambda do |_routes|
214
+ ::Rails.logger.warn(
215
+ I18n.t('doorkeeper.errors.messages.admin_authenticator_not_configured')
216
+ )
217
+
218
+ head :forbidden
219
+ end)
189
220
 
190
221
  option :resource_owner_from_credentials,
191
222
  default: (lambda do |_routes|
192
- ::Rails.logger.warn(I18n.t('doorkeeper.errors.messages.credential_flow_not_configured'))
223
+ ::Rails.logger.warn(
224
+ I18n.t('doorkeeper.errors.messages.credential_flow_not_configured')
225
+ )
226
+
193
227
  nil
194
228
  end)
229
+ option :before_successful_authorization, default: ->(_context) {}
230
+ option :after_successful_authorization, default: ->(_context) {}
195
231
  option :before_successful_strategy_response, default: ->(_request) {}
196
232
  option :after_successful_strategy_response,
197
233
  default: ->(_request, _response) {}
198
234
  option :skip_authorization, default: ->(_routes) {}
199
235
  option :access_token_expires_in, default: 7200
200
- option :custom_access_token_expires_in, default: ->(_app) { nil }
236
+ option :custom_access_token_expires_in, default: ->(_context) { nil }
201
237
  option :authorization_code_expires_in, default: 600
202
238
  option :orm, default: :active_record
203
239
  option :native_redirect_uri, default: 'urn:ietf:wg:oauth:2.0:oob'
@@ -232,7 +268,6 @@ doorkeeper.
232
268
  #
233
269
  option :force_ssl_in_redirect_uri, default: !Rails.env.development?
234
270
 
235
-
236
271
  # Use a custom class for generating the access token.
237
272
  # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
238
273
  #
@@ -251,20 +286,35 @@ doorkeeper.
251
286
  default: 'ActionController::Base'
252
287
 
253
288
  attr_reader :reuse_access_token
289
+ attr_reader :api_only
290
+ attr_reader :enforce_content_type
291
+
292
+ def api_only
293
+ @api_only ||= false
294
+ end
295
+
296
+ def enforce_content_type
297
+ @enforce_content_type ||= false
298
+ end
254
299
 
255
300
  def refresh_token_enabled?
256
- @refresh_token_enabled ||= false
257
- !!@refresh_token_enabled
301
+ if defined?(@refresh_token_enabled)
302
+ @refresh_token_enabled
303
+ else
304
+ false
305
+ end
306
+ end
307
+
308
+ def enforce_configured_scopes?
309
+ !!(defined?(@enforce_configured_scopes) && @enforce_configured_scopes)
258
310
  end
259
311
 
260
312
  def enable_application_owner?
261
- @enable_application_owner ||= false
262
- !!@enable_application_owner
313
+ !!(defined?(@enable_application_owner) && @enable_application_owner)
263
314
  end
264
315
 
265
316
  def confirm_application_owner?
266
- @confirm_application_owner ||= false
267
- !!@confirm_application_owner
317
+ !!(defined?(@confirm_application_owner) && @confirm_application_owner)
268
318
  end
269
319
 
270
320
  def default_scopes
@@ -280,7 +330,7 @@ doorkeeper.
280
330
  end
281
331
 
282
332
  def client_credentials_methods
283
- @client_credentials ||= %i[from_basic from_params]
333
+ @client_credentials_methods ||= %i[from_basic from_params]
284
334
  end
285
335
 
286
336
  def access_token_methods
@@ -288,11 +338,11 @@ doorkeeper.
288
338
  end
289
339
 
290
340
  def authorization_response_types
291
- @authorization_response_types ||= calculate_authorization_response_types
341
+ @authorization_response_types ||= calculate_authorization_response_types.freeze
292
342
  end
293
343
 
294
344
  def token_grant_types
295
- @token_grant_types ||= calculate_token_grant_types
345
+ @token_grant_types ||= calculate_token_grant_types.freeze
296
346
  end
297
347
 
298
348
  private
@@ -17,6 +17,10 @@ module Doorkeeper
17
17
 
18
18
  if defined?(Sprockets) && Sprockets::VERSION.chr.to_i >= 4
19
19
  initializer 'doorkeeper.assets.precompile' do |app|
20
+ # Force users to use:
21
+ # //= link doorkeeper/admin/application.css
22
+ # in Doorkeeper 5 for Sprockets 4 instead of precompile.
23
+ # Add note to official docs & Wiki
20
24
  app.config.assets.precompile += %w[
21
25
  doorkeeper/application.css
22
26
  doorkeeper/admin/application.css
@@ -36,10 +36,7 @@ module Doorkeeper
36
36
  end
37
37
  end
38
38
 
39
- class UnableToGenerateToken < DoorkeeperError
40
- end
41
-
42
- class TokenGeneratorNotFound < DoorkeeperError
43
- end
39
+ UnableToGenerateToken = Class.new(DoorkeeperError)
40
+ TokenGeneratorNotFound = Class.new(DoorkeeperError)
44
41
  end
45
42
  end
@@ -31,7 +31,7 @@ module Doorkeeper
31
31
  end
32
32
 
33
33
  def doorkeeper_token
34
- @_doorkeeper_token ||= OAuth::Token.authenticate(
34
+ @doorkeeper_token ||= OAuth::Token.authenticate(
35
35
  decorated_request,
36
36
  *Doorkeeper.configuration.access_token_methods
37
37
  )
@@ -30,11 +30,11 @@ module Doorkeeper
30
30
 
31
31
  # :doc:
32
32
  def doorkeeper_token
33
- @token ||= OAuth::Token.authenticate request, *config_methods
33
+ @doorkeeper_token ||= OAuth::Token.authenticate request, *config_methods
34
34
  end
35
35
 
36
36
  def config_methods
37
- @methods ||= Doorkeeper.configuration.access_token_methods
37
+ @config_methods ||= Doorkeeper.configuration.access_token_methods
38
38
  end
39
39
 
40
40
  def get_error_response_from_exception(exception)
@@ -51,6 +51,11 @@ module Doorkeeper
51
51
  def skip_authorization?
52
52
  !!instance_exec([@server.current_resource_owner, @pre_auth.client], &Doorkeeper.configuration.skip_authorization)
53
53
  end
54
+
55
+ def enforce_content_type
56
+ return if request.content_type == 'application/x-www-form-urlencoded'
57
+ render json: {}, status: :unsupported_media_type
58
+ end
54
59
  end
55
60
  end
56
61
  end
@@ -9,6 +9,15 @@ module Doorkeeper
9
9
  include Models::Orderable
10
10
  include Models::Scopes
11
11
 
12
+ # never uses pkce, if pkce migrations were not generated
13
+ def uses_pkce?
14
+ pkce_supported? && code_challenge.present?
15
+ end
16
+
17
+ def pkce_supported?
18
+ respond_to? :code_challenge
19
+ end
20
+
12
21
  module ClassMethods
13
22
  # Searches for Doorkeeper::AccessGrant record with the
14
23
  # specific token value.
@@ -21,6 +30,68 @@ module Doorkeeper
21
30
  def by_token(token)
22
31
  find_by(token: token.to_s)
23
32
  end
33
+
34
+ # Revokes AccessGrant records that have not been revoked and associated
35
+ # with the specific Application and Resource Owner.
36
+ #
37
+ # @param application_id [Integer]
38
+ # ID of the Application
39
+ # @param resource_owner [ActiveRecord::Base]
40
+ # instance of the Resource Owner model
41
+ #
42
+ def revoke_all_for(application_id, resource_owner, clock = Time)
43
+ where(application_id: application_id,
44
+ resource_owner_id: resource_owner.id,
45
+ revoked_at: nil).
46
+ update_all(revoked_at: clock.now.utc)
47
+ end
48
+
49
+ # Implements PKCE code_challenge encoding without base64 padding as described in the spec.
50
+ # https://tools.ietf.org/html/rfc7636#appendix-A
51
+ # Appendix A. Notes on Implementing Base64url Encoding without Padding
52
+ #
53
+ # This appendix describes how to implement a base64url-encoding
54
+ # function without padding, based upon the standard base64-encoding
55
+ # function that uses padding.
56
+ #
57
+ # To be concrete, example C# code implementing these functions is shown
58
+ # below. Similar code could be used in other languages.
59
+ #
60
+ # static string base64urlencode(byte [] arg)
61
+ # {
62
+ # string s = Convert.ToBase64String(arg); // Regular base64 encoder
63
+ # s = s.Split('=')[0]; // Remove any trailing '='s
64
+ # s = s.Replace('+', '-'); // 62nd char of encoding
65
+ # s = s.Replace('/', '_'); // 63rd char of encoding
66
+ # return s;
67
+ # }
68
+ #
69
+ # An example correspondence between unencoded and encoded values
70
+ # follows. The octet sequence below encodes into the string below,
71
+ # which when decoded, reproduces the octet sequence.
72
+ #
73
+ # 3 236 255 224 193
74
+ #
75
+ # A-z_4ME
76
+ #
77
+ # https://ruby-doc.org/stdlib-2.1.3/libdoc/base64/rdoc/Base64.html#method-i-urlsafe_encode64
78
+ #
79
+ # urlsafe_encode64(bin)
80
+ # Returns the Base64-encoded version of bin. This method complies with
81
+ # “Base 64 Encoding with URL and Filename Safe Alphabet” in RFC 4648.
82
+ # The alphabet uses '-' instead of '+' and '_' instead of '/'.
83
+
84
+ # @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
85
+ #
86
+ # @return [#to_s] An encoded code challenge based on the provided verifier suitable for PKCE validation
87
+ def generate_code_challenge(code_verifier)
88
+ padded_result = Base64.urlsafe_encode64(Digest::SHA256.digest(code_verifier))
89
+ padded_result.split('=')[0] # Remove any trailing '='
90
+ end
91
+
92
+ def pkce_supported?
93
+ new.pkce_supported?
94
+ end
24
95
  end
25
96
  end
26
97
  end
@@ -70,30 +70,34 @@ module Doorkeeper
70
70
  else
71
71
  resource_owner_or_id
72
72
  end
73
- token = last_authorized_token_for(application.try(:id), resource_owner_id)
74
- if token && scopes_match?(token.scopes, scopes, application.try(:scopes))
75
- token
73
+
74
+ tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
75
+ tokens.detect do |token|
76
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
76
77
  end
77
78
  end
78
79
 
79
- # Checks whether the token scopes match the scopes from the parameters or
80
- # Application scopes (if present).
80
+ # Checks whether the token scopes match the scopes from the parameters
81
81
  #
82
82
  # @param token_scopes [#to_s]
83
83
  # set of scopes (any object that responds to `#to_s`)
84
- # @param param_scopes [String]
84
+ # @param param_scopes [Doorkeeper::OAuth::Scopes]
85
85
  # scopes from params
86
- # @param app_scopes [String]
86
+ # @param app_scopes [Doorkeeper::OAuth::Scopes]
87
87
  # Application scopes
88
88
  #
89
- # @return [Boolean] true if all scopes are blank or matches
89
+ # @return [Boolean] true if the param scopes match the token scopes,
90
+ # and all the param scopes are defined in the application (or in the
91
+ # server configuration if the application doesn't define any scopes),
90
92
  # and false in other cases
91
93
  #
92
94
  def scopes_match?(token_scopes, param_scopes, app_scopes)
93
- (!token_scopes.present? && !param_scopes.present?) ||
94
- Doorkeeper::OAuth::Helpers::ScopeChecker.match?(
95
- token_scopes.to_s,
96
- param_scopes,
95
+ return true if token_scopes.empty? && param_scopes.empty?
96
+
97
+ (token_scopes.sort == param_scopes.sort) &&
98
+ Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
99
+ param_scopes.to_s,
100
+ Doorkeeper.configuration.scopes,
97
101
  app_scopes
98
102
  )
99
103
  end
@@ -118,9 +122,8 @@ module Doorkeeper
118
122
  def find_or_create_for(application, resource_owner_id, scopes, expires_in, use_refresh_token)
119
123
  if Doorkeeper.configuration.reuse_access_token
120
124
  access_token = matching_token_for(application, resource_owner_id, scopes)
121
- if access_token && !access_token.expired?
122
- return access_token
123
- end
125
+
126
+ return access_token if access_token && !access_token.expired?
124
127
  end
125
128
 
126
129
  create!(
@@ -132,7 +135,7 @@ module Doorkeeper
132
135
  )
133
136
  end
134
137
 
135
- # Looking for not revoked Access Token record that belongs to specific
138
+ # Looking for not revoked Access Token records that belongs to specific
136
139
  # Application and Resource Owner.
137
140
  #
138
141
  # @param application_id [Integer]
@@ -140,14 +143,28 @@ module Doorkeeper
140
143
  # @param resource_owner_id [Integer]
141
144
  # ID of the Resource Owner model instance
142
145
  #
146
+ # @return [Doorkeeper::AccessToken] array of matching AccessToken objects
147
+ #
148
+ def authorized_tokens_for(application_id, resource_owner_id)
149
+ ordered_by(:created_at, :desc)
150
+ .where(application_id: application_id,
151
+ resource_owner_id: resource_owner_id,
152
+ revoked_at: nil)
153
+ end
154
+
155
+ # Convenience method for backwards-compatibility, return the last
156
+ # matching token for the given Application and Resource Owner.
157
+ #
158
+ # @param application_id [Integer]
159
+ # ID of the Application model instance
160
+ # @param resource_owner_id [Integer]
161
+ # ID of the Resource Owner model instance
162
+ #
143
163
  # @return [Doorkeeper::AccessToken, nil] matching AccessToken object or
144
164
  # nil if nothing was found
145
165
  #
146
166
  def last_authorized_token_for(application_id, resource_owner_id)
147
- ordered_by(:created_at, :desc).
148
- find_by(application_id: application_id,
149
- resource_owner_id: resource_owner_id,
150
- revoked_at: nil)
167
+ authorized_tokens_for(application_id, resource_owner_id).first
151
168
  end
152
169
  end
153
170
 
@@ -156,7 +173,7 @@ module Doorkeeper
156
173
  # The OAuth 2.0 Authorization Framework: Bearer Token Usage
157
174
  #
158
175
  def token_type
159
- 'bearer'
176
+ 'Bearer'
160
177
  end
161
178
 
162
179
  def use_refresh_token?
@@ -170,8 +187,8 @@ module Doorkeeper
170
187
  def as_json(_options = {})
171
188
  {
172
189
  resource_owner_id: resource_owner_id,
173
- scopes: scopes,
174
- expires_in_seconds: expires_in_seconds,
190
+ scope: scopes,
191
+ expires_in: expires_in_seconds,
175
192
  application: { uid: application.try(:uid) },
176
193
  created_at: created_at.to_i
177
194
  }
@@ -10,6 +10,9 @@ module Doorkeeper
10
10
  # Returns an instance of the Doorkeeper::Application with
11
11
  # specific UID and secret.
12
12
  #
13
+ # Public/Non-confidential applications will only find by uid if secret is
14
+ # blank.
15
+ #
13
16
  # @param uid [#to_s] UID (any object that responds to `#to_s`)
14
17
  # @param secret [#to_s] secret (any object that responds to `#to_s`)
15
18
  #
@@ -17,7 +20,11 @@ module Doorkeeper
17
20
  # if there is no record with such credentials
18
21
  #
19
22
  def by_uid_and_secret(uid, secret)
20
- find_by(uid: uid.to_s, secret: secret.to_s)
23
+ app = by_uid(uid)
24
+ return unless app
25
+ return app if secret.blank? && !app.confidential?
26
+ return unless app.secret == secret
27
+ app
21
28
  end
22
29
 
23
30
  # Returns an instance of the Doorkeeper::Application with specific UID.
@@ -10,7 +10,7 @@ module Doorkeeper
10
10
  end
11
11
 
12
12
  def includes_scope?(*required_scopes)
13
- required_scopes.blank? || required_scopes.any? { |s| scopes.exists?(s.to_s) }
13
+ required_scopes.blank? || required_scopes.any? { |scope| scopes.exists?(scope.to_s) }
14
14
  end
15
15
  end
16
16
  end
@@ -5,18 +5,12 @@ module Doorkeeper
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
7
  def initialize(pre_auth, resource_owner)
8
- @pre_auth = pre_auth
8
+ @pre_auth = pre_auth
9
9
  @resource_owner = resource_owner
10
10
  end
11
11
 
12
12
  def issue_token
13
- @token ||= AccessGrant.create!(
14
- application_id: pre_auth.client.id,
15
- resource_owner_id: resource_owner.id,
16
- expires_in: configuration.authorization_code_expires_in,
17
- redirect_uri: pre_auth.redirect_uri,
18
- scopes: pre_auth.scopes.to_s
19
- )
13
+ @token ||= AccessGrant.create! access_grant_attributes
20
14
  end
21
15
 
22
16
  def native_redirect
@@ -26,6 +20,35 @@ module Doorkeeper
26
20
  def configuration
27
21
  Doorkeeper.configuration
28
22
  end
23
+
24
+ private
25
+
26
+ def authorization_code_expires_in
27
+ configuration.authorization_code_expires_in
28
+ end
29
+
30
+ def access_grant_attributes
31
+ pkce_attributes.merge application_id: pre_auth.client.id,
32
+ resource_owner_id: resource_owner.id,
33
+ expires_in: authorization_code_expires_in,
34
+ redirect_uri: pre_auth.redirect_uri,
35
+ scopes: pre_auth.scopes.to_s
36
+ end
37
+
38
+ def pkce_attributes
39
+ return {} unless pkce_supported?
40
+
41
+ {
42
+ code_challenge: pre_auth.code_challenge,
43
+ code_challenge_method: pre_auth.code_challenge_method
44
+ }
45
+ end
46
+
47
+ # ensures firstly, if migration with additional pcke columns was
48
+ # generated and migrated
49
+ def pkce_supported?
50
+ Doorkeeper::AccessGrant.pkce_supported?
51
+ end
29
52
  end
30
53
  end
31
54
  end
@@ -0,0 +1,15 @@
1
+ module Doorkeeper
2
+ module OAuth
3
+ module Authorization
4
+ class Context
5
+ attr_reader :client, :grant_type, :scopes
6
+
7
+ def initialize(client, grant_type, scopes)
8
+ @client = client
9
+ @grant_type = grant_type
10
+ @scopes = scopes
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -5,24 +5,34 @@ module Doorkeeper
5
5
  attr_accessor :pre_auth, :resource_owner, :token
6
6
 
7
7
  class << self
8
- def access_token_expires_in(server, pre_auth_or_oauth_client)
9
- if (expiration = custom_expiration(server, pre_auth_or_oauth_client))
10
- expiration
11
- else
12
- server.access_token_expires_in
13
- end
14
- end
15
-
16
- private
17
-
18
- def custom_expiration(server, pre_auth_or_oauth_client)
8
+ def build_context(pre_auth_or_oauth_client, grant_type, scopes)
19
9
  oauth_client = if pre_auth_or_oauth_client.respond_to?(:client)
20
10
  pre_auth_or_oauth_client.client
21
11
  else
22
12
  pre_auth_or_oauth_client
23
13
  end
24
14
 
25
- server.custom_access_token_expires_in.call(oauth_client)
15
+ Doorkeeper::OAuth::Authorization::Context.new(
16
+ oauth_client,
17
+ grant_type,
18
+ scopes
19
+ )
20
+ end
21
+
22
+ def access_token_expires_in(server, context)
23
+ if (expiration = server.custom_access_token_expires_in.call(context))
24
+ expiration
25
+ else
26
+ server.access_token_expires_in
27
+ end
28
+ end
29
+
30
+ def refresh_token_enabled?(server, context)
31
+ if server.refresh_token_enabled?.respond_to? :call
32
+ server.refresh_token_enabled?.call(context)
33
+ else
34
+ !!server.refresh_token_enabled?
35
+ end
26
36
  end
27
37
  end
28
38
 
@@ -32,18 +42,23 @@ module Doorkeeper
32
42
  end
33
43
 
34
44
  def issue_token
45
+ context = self.class.build_context(
46
+ pre_auth.client,
47
+ Doorkeeper::OAuth::IMPLICIT,
48
+ pre_auth.scopes
49
+ )
35
50
  @token ||= AccessToken.find_or_create_for(
36
51
  pre_auth.client,
37
52
  resource_owner.id,
38
53
  pre_auth.scopes,
39
- self.class.access_token_expires_in(configuration, pre_auth),
54
+ self.class.access_token_expires_in(configuration, context),
40
55
  false
41
56
  )
42
57
  end
43
58
 
44
59
  def native_redirect
45
60
  {
46
- controller: 'doorkeeper/token_info',
61
+ controller: controller,
47
62
  action: :show,
48
63
  access_token: token.token
49
64
  }
@@ -54,6 +69,13 @@ module Doorkeeper
54
69
  def configuration
55
70
  Doorkeeper.configuration
56
71
  end
72
+
73
+ def controller
74
+ @controller ||= begin
75
+ mapping = Doorkeeper::Rails::Routes.mapping[:token_info] || {}
76
+ mapping[:controllers] || 'doorkeeper/token_info'
77
+ end
78
+ end
57
79
  end
58
80
  end
59
81
  end