doorkeeper 5.2.0.rc1 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of doorkeeper might be problematic. Click here for more details.

Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +1 -1
  3. data/CHANGELOG.md +33 -2
  4. data/CONTRIBUTING.md +7 -0
  5. data/Dangerfile +1 -1
  6. data/Dockerfile +29 -0
  7. data/Gemfile +1 -1
  8. data/README.md +9 -1
  9. data/app/controllers/doorkeeper/application_controller.rb +1 -1
  10. data/app/controllers/doorkeeper/application_metal_controller.rb +2 -1
  11. data/app/controllers/doorkeeper/authorizations_controller.rb +14 -7
  12. data/app/controllers/doorkeeper/tokens_controller.rb +14 -1
  13. data/config/locales/en.yml +5 -1
  14. data/doorkeeper.gemspec +8 -0
  15. data/gemfiles/rails_6_0.gemfile +1 -1
  16. data/lib/doorkeeper/config.rb +64 -9
  17. data/lib/doorkeeper/errors.rb +13 -18
  18. data/lib/doorkeeper/helpers/controller.rb +6 -2
  19. data/lib/doorkeeper/models/access_token_mixin.rb +43 -2
  20. data/lib/doorkeeper/oauth/authorization/code.rb +1 -5
  21. data/lib/doorkeeper/oauth/authorization_code_request.rb +18 -9
  22. data/lib/doorkeeper/oauth/base_request.rb +2 -0
  23. data/lib/doorkeeper/oauth/client_credentials/creator.rb +14 -0
  24. data/lib/doorkeeper/oauth/client_credentials/validation.rb +8 -0
  25. data/lib/doorkeeper/oauth/code_request.rb +5 -11
  26. data/lib/doorkeeper/oauth/invalid_request_response.rb +43 -0
  27. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  28. data/lib/doorkeeper/oauth/pre_authorization.rb +70 -37
  29. data/lib/doorkeeper/oauth/refresh_token_request.rb +5 -2
  30. data/lib/doorkeeper/oauth/token_introspection.rb +16 -7
  31. data/lib/doorkeeper/oauth/token_request.rb +4 -18
  32. data/lib/doorkeeper/orm/active_record/application.rb +1 -1
  33. data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +61 -0
  34. data/lib/doorkeeper/orm/active_record.rb +2 -2
  35. data/lib/doorkeeper/request/authorization_code.rb +2 -0
  36. data/lib/doorkeeper/request.rb +6 -11
  37. data/lib/doorkeeper/server.rb +2 -6
  38. data/lib/doorkeeper/version.rb +1 -1
  39. data/lib/doorkeeper.rb +1 -0
  40. data/lib/generators/doorkeeper/templates/initializer.rb +88 -43
  41. data/lib/generators/doorkeeper/templates/migration.rb.erb +1 -1
  42. data/spec/controllers/authorizations_controller_spec.rb +140 -61
  43. data/spec/controllers/protected_resources_controller_spec.rb +3 -3
  44. data/spec/controllers/tokens_controller_spec.rb +140 -40
  45. data/spec/dummy/config/initializers/doorkeeper.rb +47 -20
  46. data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +1 -1
  47. data/spec/lib/config_spec.rb +32 -1
  48. data/spec/lib/oauth/authorization_code_request_spec.rb +11 -1
  49. data/spec/lib/oauth/base_request_spec.rb +33 -16
  50. data/spec/lib/oauth/client_credentials/creator_spec.rb +3 -0
  51. data/spec/lib/oauth/code_request_spec.rb +27 -28
  52. data/spec/lib/oauth/invalid_request_response_spec.rb +75 -0
  53. data/spec/lib/oauth/pre_authorization_spec.rb +80 -55
  54. data/spec/lib/oauth/refresh_token_request_spec.rb +1 -0
  55. data/spec/lib/oauth/token_request_spec.rb +20 -17
  56. data/spec/lib/server_spec.rb +0 -12
  57. data/spec/requests/endpoints/authorization_spec.rb +21 -5
  58. data/spec/requests/endpoints/token_spec.rb +1 -1
  59. data/spec/requests/flows/authorization_code_errors_spec.rb +1 -0
  60. data/spec/requests/flows/authorization_code_spec.rb +77 -23
  61. data/spec/requests/flows/client_credentials_spec.rb +38 -0
  62. data/spec/requests/flows/implicit_grant_errors_spec.rb +22 -10
  63. data/spec/requests/flows/implicit_grant_spec.rb +9 -8
  64. data/spec/requests/flows/password_spec.rb +37 -0
  65. data/spec/requests/flows/refresh_token_spec.rb +1 -1
  66. data/spec/support/helpers/request_spec_helper.rb +14 -2
  67. data/spec/validators/redirect_uri_validator_spec.rb +1 -1
  68. metadata +15 -6
  69. data/app/validators/redirect_uri_validator.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8d957f89709f38c434bcaa2f50d2b7a540ed613c37c49a7206cbab07c32c240
4
- data.tar.gz: f1945351442b325395da930863b402cb45091f576075ffe0b71dbc285911d846
3
+ metadata.gz: c5184a79d20bb22a118af7f53e465f3d16b4e8796819bc3d3787118b65e2faa5
4
+ data.tar.gz: 36dbd460edaad12e3550210d3edfa1c2f6b82bfa600c7a6fc3c1e730bc7d34c1
5
5
  SHA512:
6
- metadata.gz: 97b9c9d403403af0227f670b66876be06dac554beeb024f507e3c460a0fcd6c5eeb75b8747946ff622065791992ce1bf9c30f8366f964e57545e229366533048
7
- data.tar.gz: f235dee893b1ad79ad1873006e68e0ad508a25f676c4d6ecd6f867cca0ec8c5f1776420d79e0efb8cefc7309b1f467ef34de3382249c698fd842872c3eea5254
6
+ metadata.gz: 2b1183b93495fcaf34b7d1761d1e605e9203bdcfa483ebc6b3c11895b781ace879439f84d92d05dc9321bf28014b64e4c2a63666a20a389b26a34f1a5dc3c048
7
+ data.tar.gz: 75959d91b24d5a34538e9bf2d5fca263bbd9338a1cf20d808a6737b0ec4ee507f2d07ee47031d09ab7211e62be5803223f2a28d4099e91008741606162c3dafa
data/Appraisals CHANGED
@@ -16,7 +16,7 @@ appraise "rails-5-2" do
16
16
  end
17
17
 
18
18
  appraise "rails-6-0" do
19
- gem "rails", "~> 6.0.0.beta3"
19
+ gem "rails", "~> 6.0.0"
20
20
  gem "sqlite3", "~> 1.4", platform: %i[ruby mswin mingw x64_mingw]
21
21
 
22
22
  # TODO: Remove when rspec-rails 4.0 released
data/CHANGELOG.md CHANGED
@@ -7,7 +7,38 @@ User-visible changes worth mentioning.
7
7
 
8
8
  ## master
9
9
 
10
- - [#1260], [#1261] Improve Token Introspection configuration option (access to tokens, client).
10
+ - [#PR ID] Your PR description here.
11
+
12
+ ## 5.2.0
13
+
14
+ - [#1305] Make `Doorkeeper::ApplicationController` to inherit from `ActionController::API` in cases
15
+ when `api_mode` enabled (fixes #1302).
16
+
17
+ ## 5.2.0.rc3
18
+
19
+ - [#1298] Slice strong params so doesn't error with Rails forms.
20
+ - [#1300] Limiting access to attributes of pre_authorization.
21
+ - [#1296] Adding client_id to strong parameters.
22
+ - [#1293] Move ar specific redirect uri validator to ar orm directory.
23
+ - [#1288] Allow to pass attributes to the `Doorkeeper::OAuth::PreAuthorization#as_json` method to customize
24
+ the PreAuthorization response.
25
+ - [#1286] Add ability to customize grant flows per application (OAuth client) (#1245 , #1207)
26
+ - [#1283] Allow to customize base class for `Doorkeeper::ApplicationMetalController` (new configuration
27
+ option called `base_metal_controller` (fix #1273).
28
+ - [#1277] Prevent requested scope be empty on authorization request, handle and add description for invalid request.
29
+
30
+ ## 5.2.0.rc2
31
+
32
+ - [#1270] Find matching tokens in batches for `reuse_access_token` option (fix #1193).
33
+ - [#1271] Reintroduce existing token revocation for client credentials.
34
+ - [#1269] Update initializer template documentation.
35
+ - [#1266] Use strong parameters within pre-authorization.
36
+ - [#1264] Add :before_successful_authorization and :after_successful_authorization hooks in TokensController
37
+ - [#1263] Response properly when introspection fails and fix configurations's user guide.
38
+
39
+ ## 5.2.0.rc1
40
+
41
+ - [#1260], [#1262] Improve Token Introspection configuration option (access to tokens, client).
11
42
  - [#1257] Add constraint configuration when using client authentication on introspection endpoint.
12
43
  - [#1252] Returning `unauthorized` when the revocation of the token should not be performed due to wrong permissions.
13
44
  - [#1249] Specify case sensitive uniqueness to remove Rails 6 deprecation message
@@ -31,7 +62,7 @@ User-visible changes worth mentioning.
31
62
 
32
63
  - [#1208] Unify hashing implementation into secret storing strategies
33
64
 
34
- **[IMPORTANT]**: If you have been using the master branch of doorkeeper with bcrypt in your Gemfile.lock,
65
+ **[IMPORTANT]** If you have been using the master branch of doorkeeper with bcrypt in your Gemfile.lock,
35
66
  your application secrets have been hashed using BCrypt. To restore this behavior, use the initializer option
36
67
  `use_application_hashing using: 'Doorkeeper::SecretStoring::BCrypt`.
37
68
 
data/CONTRIBUTING.md CHANGED
@@ -7,6 +7,13 @@ Fork, then clone the repo:
7
7
 
8
8
  git clone git@github.com:your-username/doorkeeper.git
9
9
 
10
+ ### Docker Setup
11
+
12
+ Build the container image with: `docker build --pull -t doorkeeper:test .`
13
+ Run the tests with: `docker run -it --rm doorkeeper:test`
14
+
15
+ ### Local Setup
16
+
10
17
  Set up Ruby dependencies via Bundler
11
18
 
12
19
  bundle install
data/Dangerfile CHANGED
@@ -11,7 +11,7 @@ def changelog_entry_example
11
11
  .sub(/[?.!,;]?$/, '')
12
12
  .capitalize
13
13
 
14
- "- [##{pr_number}]: #{pr_title}."
14
+ "- [##{pr_number}] #{pr_title}."
15
15
  end
16
16
 
17
17
  # --------------------------------------------------------------------------------------------------------------------
data/Dockerfile ADDED
@@ -0,0 +1,29 @@
1
+ FROM ruby:2.6.3-alpine3.9
2
+
3
+ RUN apk add --no-cache \
4
+ ca-certificates \
5
+ wget \
6
+ openssl \
7
+ bash \
8
+ build-base \
9
+ git \
10
+ sqlite-dev \
11
+ tzdata
12
+
13
+ ENV LANG en_US.UTF-8
14
+ ENV LANGUAGE en_US:en
15
+ ENV LC_ALL en_US.UTF-8
16
+
17
+ ENV BUNDLER_VERSION 2.0.1
18
+ RUN gem install bundler -v ${BUNDLER_VERSION} -i /usr/local/lib/ruby/gems/$(ls /usr/local/lib/ruby/gems) --force
19
+
20
+ WORKDIR /srv
21
+
22
+ COPY Gemfile doorkeeper.gemspec /srv/
23
+ COPY lib/doorkeeper/version.rb /srv/lib/doorkeeper/version.rb
24
+
25
+ RUN bundle install
26
+
27
+ COPY . /srv/
28
+
29
+ CMD ["rake"]
data/Gemfile CHANGED
@@ -5,7 +5,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
6
  gemspec
7
7
 
8
- gem "rails", "~> 6.0.0.rc1"
8
+ gem "rails", "~> 6.0.0"
9
9
 
10
10
  # TODO: Remove when rspec-rails 4.0 released
11
11
  gem "rspec-core", github: "rspec/rspec-core"
data/README.md CHANGED
@@ -21,10 +21,11 @@ Supported features:
21
21
  - [Implicit grant](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.2)
22
22
  - [Resource Owner Password Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.3)
23
23
  - [Client Credentials](http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-4.4)
24
- - [Proof Key for Code Exchange](https://tools.ietf.org/html/rfc7636)
25
24
  - [OAuth 2.0 Token Revocation](http://tools.ietf.org/html/rfc7009)
26
25
  - [OAuth 2.0 Token Introspection](https://tools.ietf.org/html/rfc7662)
27
26
  - [OAuth 2.0 Threat Model and Security Considerations](http://tools.ietf.org/html/rfc6819)
27
+ - [OAuth 2.0 for Native Apps](https://tools.ietf.org/html/draft-ietf-oauth-native-apps-10)
28
+ - [Proof Key for Code Exchange by OAuth Public Clients](https://tools.ietf.org/html/rfc7636)
28
29
 
29
30
  ## Table of Contents
30
31
 
@@ -93,6 +94,7 @@ Doorkeeper supports Active Record by default, but can be configured to work with
93
94
  | MongoDB | [doorkeeper-gem/doorkeeper-mongodb](https://github.com/doorkeeper-gem/doorkeeper-mongodb) |
94
95
  | Sequel | [nbulaj/doorkeeper-sequel](https://github.com/nbulaj/doorkeeper-sequel) |
95
96
  | Couchbase | [acaprojects/doorkeeper-couchbase](https://github.com/acaprojects/doorkeeper-couchbase) |
97
+ | RethinkDB | [aca-labs/doorkeeper-rethinkdb](https://github.com/aca-labs/doorkeeper-rethinkdb) |
96
98
 
97
99
  ## Extensions
98
100
 
@@ -136,6 +138,12 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
136
138
 
137
139
  > If you prefer not to deal with the gory details of OAuth 2, need dedicated customer support & consulting, try the cloud-based SaaS version: [https://oauth.io](https://oauth.io/?utm_source=doorkeeper-gem)
138
140
 
141
+ <br>
142
+
143
+ <a href="https://www.wealthsimple.com/?utm_source=doorkeeper-gem" target="_blank"><img src="https://wealthsimple.s3.amazonaws.com/branding/medium-black.svg"/></a>
144
+
145
+ > Wealthsimple is a financial company on a mission to help everyone achieve financial freedom by providing products and advice that are accessible and affordable. Using smart technology, Wealthsimple takes financial services that are often confusing, opaque and expensive and makes them simple, transparent, and low-cost. See what Investing on Autopilot is all about: [https://www.wealthsimple.com](https://www.wealthsimple.com/?utm_source=doorkeeper-gem)
146
+
139
147
  ## Development
140
148
 
141
149
  To run the local engine server:
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Doorkeeper
4
4
  class ApplicationController <
5
- Doorkeeper.configuration.base_controller.constantize
5
+ Doorkeeper.configuration.resolve_controller(:base)
6
6
  include Helpers::Controller
7
7
 
8
8
  unless Doorkeeper.configuration.api_only
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Doorkeeper
4
- class ApplicationMetalController < ActionController::API
4
+ class ApplicationMetalController <
5
+ Doorkeeper.configuration.resolve_controller(:base_metal)
5
6
  include Helpers::Controller
6
7
 
7
8
  before_action :enforce_content_type,
@@ -12,7 +12,6 @@ module Doorkeeper
12
12
  end
13
13
  end
14
14
 
15
- # TODO: Handle raise invalid authorization
16
15
  def create
17
16
  redirect_or_render authorize_response
18
17
  end
@@ -66,9 +65,16 @@ module Doorkeeper
66
65
  end
67
66
 
68
67
  def pre_auth
69
- @pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration,
70
- server.client_via_uid,
71
- params)
68
+ @pre_auth ||= OAuth::PreAuthorization.new(Doorkeeper.configuration, pre_auth_params)
69
+ end
70
+
71
+ def pre_auth_params
72
+ params.slice(*pre_auth_param_fields).permit(*pre_auth_param_fields)
73
+ end
74
+
75
+ def pre_auth_param_fields
76
+ %i[client_id response_type redirect_uri scope state code_challenge
77
+ code_challenge_method]
72
78
  end
73
79
 
74
80
  def authorization
@@ -81,10 +87,11 @@ module Doorkeeper
81
87
 
82
88
  def authorize_response
83
89
  @authorize_response ||= begin
84
- authorizable = pre_auth.authorizable?
85
- before_successful_authorization if authorizable
90
+ return pre_auth.error_response unless pre_auth.authorizable?
91
+
92
+ before_successful_authorization
86
93
  auth = strategy.authorize
87
- after_successful_authorization if authorizable
94
+ after_successful_authorization
88
95
  auth
89
96
  end
90
97
  end
@@ -88,7 +88,20 @@ module Doorkeeper
88
88
  end
89
89
 
90
90
  def authorize_response
91
- @authorize_response ||= strategy.authorize
91
+ @authorize_response ||= begin
92
+ before_successful_authorization
93
+ auth = strategy.authorize
94
+ after_successful_authorization unless auth.is_a?(Doorkeeper::OAuth::ErrorResponse)
95
+ auth
96
+ end
97
+ end
98
+
99
+ def after_successful_authorization
100
+ Doorkeeper.configuration.after_successful_authorization.call(self)
101
+ end
102
+
103
+ def before_successful_authorization
104
+ Doorkeeper.configuration.before_successful_authorization.call(self)
92
105
  end
93
106
 
94
107
  def revocation_error_response
@@ -88,7 +88,11 @@ en:
88
88
  errors:
89
89
  messages:
90
90
  # Common error messages
91
- invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
91
+ invalid_request:
92
+ unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
93
+ missing_param: 'Missing required parameter: #{value}.'
94
+ not_support_pkce: 'Invalid code_verifier parameter. Server does not support pkce.'
95
+ request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
92
96
  invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
93
97
  unauthorized_client: 'The client is not authorized to perform this request using this method.'
94
98
  access_denied: 'The resource owner or authorization server denied the request.'
data/doorkeeper.gemspec CHANGED
@@ -18,6 +18,14 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = `git ls-files -- spec/*`.split("\n")
19
19
  gem.require_paths = ["lib"]
20
20
 
21
+ gem.metadata = {
22
+ "homepage_uri" => "https://github.com/doorkeeper-gem/doorkeeper",
23
+ "changelog_uri" => "https://github.com/doorkeeper-gem/doorkeeper/blob/master/CHANGELOG.md",
24
+ "source_code_uri" => "https://github.com/doorkeeper-gem/doorkeeper",
25
+ "bug_tracker_uri" => "https://github.com/doorkeeper-gem/doorkeeper/issues",
26
+ "documentation_uri" => "https://doorkeeper.gitbook.io/guides/",
27
+ }
28
+
21
29
  gem.add_dependency "railties", ">= 5"
22
30
  gem.required_ruby_version = ">= 2.4"
23
31
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "~> 6.0.0.rc1"
5
+ gem "rails", "~> 6.0.0"
6
6
  gem "rspec-core", git: "https://github.com/rspec/rspec-core.git"
7
7
  gem "rspec-expectations", git: "https://github.com/rspec/rspec-expectations.git"
8
8
  gem "rspec-mocks", git: "https://github.com/rspec/rspec-mocks.git"
@@ -259,6 +259,32 @@ module Doorkeeper
259
259
  option :grant_flows, default: %w[authorization_code client_credentials]
260
260
  option :handle_auth_errors, default: :render
261
261
 
262
+ # Allows to customize OAuth grant flows that +each+ application support.
263
+ # You can configure a custom block (or use a class respond to `#call`) that must
264
+ # return `true` in case Application instance supports requested OAuth grant flow
265
+ # during the authorization request to the server. This configuration +doesn't+
266
+ # set flows per application, it only allows to check if application supports
267
+ # specific grant flow.
268
+ #
269
+ # For example you can add an additional database column to `oauth_applications` table,
270
+ # say `t.array :grant_flows, default: []`, and store allowed grant flows that can
271
+ # be used with this application there. Then when authorization requested Doorkeeper
272
+ # will call this block to check if specific Application (passed with client_id and/or
273
+ # client_secret) is allowed to perform the request for the specific grant type
274
+ # (authorization, password, client_credentials, etc).
275
+ #
276
+ # Example of the block:
277
+ #
278
+ # ->(flow, client) { client.grant_flows.include?(flow) }
279
+ #
280
+ # In case this option invocation result is `false`, Doorkeeper server returns
281
+ # :unauthorized_client error and stops the request.
282
+ #
283
+ # @param allow_grant_flow_for_client [Proc] Block or any object respond to #call
284
+ # @return [Boolean] `true` if allow or `false` if forbid the request
285
+ #
286
+ option :allow_grant_flow_for_client, default: ->(_grant_flow, _client) { true }
287
+
262
288
  # Allows to forbid specific Application redirect URI's by custom rules.
263
289
  # Doesn't forbid any URI by default.
264
290
  #
@@ -288,7 +314,7 @@ module Doorkeeper
288
314
  option :force_ssl_in_redirect_uri, default: !Rails.env.development?
289
315
 
290
316
  # Use a custom class for generating the access token.
291
- # https://github.com/doorkeeper-gem/doorkeeper#custom-access-token-generator
317
+ # https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-access-token-generator
292
318
  #
293
319
  # @param access_token_generator [String]
294
320
  # the name of the access token generator class
@@ -306,11 +332,20 @@ module Doorkeeper
306
332
 
307
333
  # The controller Doorkeeper::ApplicationController inherits from.
308
334
  # Defaults to ActionController::Base.
309
- # https://github.com/doorkeeper-gem/doorkeeper#custom-base-controller
335
+ # https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-base-controller
310
336
  #
311
337
  # @param base_controller [String] the name of the base controller
312
338
  option :base_controller,
313
- default: "ActionController::Base"
339
+ default: (lambda do
340
+ api_only ? "ActionController::API" : "ActionController::Base"
341
+ end)
342
+
343
+ # The controller Doorkeeper::ApplicationMetalController inherits from.
344
+ # Defaults to ActionController::API.
345
+ #
346
+ # @param base_metal_controller [String] the name of the base controller
347
+ option :base_metal_controller,
348
+ default: "ActionController::API"
314
349
 
315
350
  # Allows to set blank redirect URIs for Applications in case
316
351
  # server configured to use URI-less grant flows.
@@ -322,9 +357,10 @@ module Doorkeeper
322
357
  end)
323
358
 
324
359
  # Configure protection of token introspection request.
325
- # By default token introspection is allowed only for clients that
326
- # introspected token belongs to or if access token been introspected
327
- # is a public one (doesn't belong to any client).
360
+ # By default this configuration allows to introspect a token by
361
+ # another token of the same application, or to introspect the token
362
+ # that belongs to authorized client, or access token has been introspected
363
+ # is a public one (doesn't belong to any client)
328
364
  #
329
365
  # You can define any custom rule you need or just disable token
330
366
  # introspection at all.
@@ -340,9 +376,11 @@ module Doorkeeper
340
376
  # Bearer token used to authorize the request
341
377
  #
342
378
  option :allow_token_introspection,
343
- default: (lambda do |token, authorized_client, _authorized_token|
344
- if token.application
345
- token.application == authorized_client
379
+ default: (lambda do |token, authorized_client, authorized_token|
380
+ if authorized_token
381
+ authorized_token.application == token&.application
382
+ elsif token.application
383
+ authorized_client == token.application
346
384
  else
347
385
  true
348
386
  end
@@ -381,6 +419,17 @@ module Doorkeeper
381
419
  @token_reuse_limit ||= 100
382
420
  end
383
421
 
422
+ def resolve_controller(name)
423
+ config_option = public_send(:"#{name}_controller")
424
+ controller_name = if config_option.respond_to?(:call)
425
+ instance_exec(&config_option)
426
+ else
427
+ config_option
428
+ end
429
+
430
+ controller_name.constantize
431
+ end
432
+
384
433
  def enforce_configured_scopes?
385
434
  option_set? :enforce_configured_scopes
386
435
  end
@@ -449,6 +498,12 @@ module Doorkeeper
449
498
  end
450
499
  end
451
500
 
501
+ def allow_grant_flow_for_client?(grant_flow, client)
502
+ return true unless option_defined?(:allow_grant_flow_for_client)
503
+
504
+ allow_grant_flow_for_client.call(grant_flow, client)
505
+ end
506
+
452
507
  def option_defined?(name)
453
508
  instance_variable_defined?("@#{name}")
454
509
  end
@@ -8,18 +8,6 @@ module Doorkeeper
8
8
  end
9
9
  end
10
10
 
11
- class InvalidAuthorizationStrategy < DoorkeeperError
12
- def type
13
- :unsupported_response_type
14
- end
15
- end
16
-
17
- class InvalidTokenReuse < DoorkeeperError
18
- def type
19
- :invalid_request
20
- end
21
- end
22
-
23
11
  class InvalidGrantReuse < DoorkeeperError
24
12
  def type
25
13
  :invalid_grant
@@ -32,7 +20,14 @@ module Doorkeeper
32
20
  end
33
21
  end
34
22
 
35
- class MissingRequestStrategy < DoorkeeperError
23
+ class MissingRequiredParameter < DoorkeeperError
24
+ attr_reader :missing_param
25
+
26
+ def initialize(missing_param)
27
+ super
28
+ @missing_param = missing_param
29
+ end
30
+
36
31
  def type
37
32
  :invalid_request
38
33
  end
@@ -50,10 +45,10 @@ module Doorkeeper
50
45
  TokenGeneratorNotFound = Class.new(DoorkeeperError)
51
46
  NoOrmCleaner = Class.new(DoorkeeperError)
52
47
 
53
- InvalidToken = Class.new BaseResponseError
54
- TokenExpired = Class.new InvalidToken
55
- TokenRevoked = Class.new InvalidToken
56
- TokenUnknown = Class.new InvalidToken
57
- TokenForbidden = Class.new InvalidToken
48
+ InvalidToken = Class.new(BaseResponseError)
49
+ TokenExpired = Class.new(InvalidToken)
50
+ TokenRevoked = Class.new(InvalidToken)
51
+ TokenUnknown = Class.new(InvalidToken)
52
+ TokenForbidden = Class.new(InvalidToken)
58
53
  end
59
54
  end
@@ -44,8 +44,12 @@ module Doorkeeper
44
44
  def get_error_response_from_exception(exception)
45
45
  if exception.respond_to?(:response)
46
46
  exception.response
47
+ elsif exception.type == :invalid_request
48
+ OAuth::InvalidRequestResponse.new(name: exception.type,
49
+ state: params[:state],
50
+ missing_param: exception.missing_param)
47
51
  else
48
- OAuth::ErrorResponse.new name: exception.type, state: params[:state]
52
+ OAuth::ErrorResponse.new(name: exception.type, state: params[:state])
49
53
  end
50
54
  end
51
55
 
@@ -58,7 +62,7 @@ module Doorkeeper
58
62
 
59
63
  def skip_authorization?
60
64
  !!instance_exec(
61
- [@server.current_resource_owner, @pre_auth.client],
65
+ [server.current_resource_owner, @pre_auth.client],
62
66
  &Doorkeeper.configuration.skip_authorization
63
67
  )
64
68
  end
@@ -76,9 +76,50 @@ module Doorkeeper
76
76
  end
77
77
 
78
78
  tokens = authorized_tokens_for(application.try(:id), resource_owner_id)
79
- tokens.detect do |token|
80
- scopes_match?(token.scopes, scopes, application.try(:scopes))
79
+ find_matching_token(tokens, application, scopes)
80
+ end
81
+
82
+ # Interface to enumerate access token records in batches in order not
83
+ # to bloat the memory. Could be overloaded in any ORM extension.
84
+ #
85
+ def find_access_token_in_batches(relation, *args, &block)
86
+ relation.find_in_batches(*args, &block)
87
+ end
88
+
89
+ # Enumerates AccessToken records in batches to find a matching token.
90
+ # Batching is required in order not to pollute the memory if Application
91
+ # has huge amount of associated records.
92
+ #
93
+ # ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
94
+ # database sort by created_at, so we need to load all the matching records,
95
+ # sort them and find latest one. Probably it would be better to rewrite this
96
+ # query using Time math if possible, but we n eed to consider ORM and
97
+ # different databases support.
98
+ #
99
+ # @param relation [ActiveRecord::Relation]
100
+ # Access tokens relation
101
+ # @param application [Doorkeeper::Application]
102
+ # Application instance
103
+ # @param scopes [String, Doorkeeper::OAuth::Scopes]
104
+ # set of scopes
105
+ #
106
+ # @return [Doorkeeper::AccessToken, nil] Access Token instance or
107
+ # nil if matching record was not found
108
+ #
109
+ def find_matching_token(relation, application, scopes)
110
+ return nil unless relation
111
+
112
+ matching_tokens = []
113
+
114
+ find_access_token_in_batches(relation) do |batch|
115
+ tokens = batch.select do |token|
116
+ scopes_match?(token.scopes, scopes, application.try(:scopes))
117
+ end
118
+
119
+ matching_tokens.concat(tokens)
81
120
  end
121
+
122
+ matching_tokens.max_by(&:created_at)
82
123
  end
83
124
 
84
125
  # Checks whether the token scopes match the scopes from the parameters
@@ -19,14 +19,10 @@ module Doorkeeper
19
19
  { action: :show, code: token.plaintext_token }
20
20
  end
21
21
 
22
- def configuration
23
- Doorkeeper.configuration
24
- end
25
-
26
22
  private
27
23
 
28
24
  def authorization_code_expires_in
29
- configuration.authorization_code_expires_in
25
+ Doorkeeper.configuration.authorization_code_expires_in
30
26
  end
31
27
 
32
28
  def access_grant_attributes
@@ -3,7 +3,8 @@
3
3
  module Doorkeeper
4
4
  module OAuth
5
5
  class AuthorizationCodeRequest < BaseRequest
6
- validate :attributes, error: :invalid_request
6
+ validate :pkce_support, error: :invalid_request
7
+ validate :params, error: :invalid_request
7
8
  validate :client, error: :invalid_client
8
9
  validate :grant, error: :invalid_grant
9
10
  # @see https://tools.ietf.org/html/rfc6749#section-5.2
@@ -12,6 +13,7 @@ module Doorkeeper
12
13
 
13
14
  attr_accessor :server, :grant, :client, :redirect_uri, :access_token,
14
15
  :code_verifier
16
+ attr_reader :invalid_request_reason, :missing_param
15
17
 
16
18
  def initialize(server, grant, client, parameters = {})
17
19
  @server = server
@@ -24,10 +26,6 @@ module Doorkeeper
24
26
 
25
27
  private
26
28
 
27
- def client_by_uid(parameters)
28
- Doorkeeper::Application.by_uid(parameters[:client_id])
29
- end
30
-
31
29
  def before_successful_response
32
30
  grant.transaction do
33
31
  grant.lock!
@@ -42,11 +40,22 @@ module Doorkeeper
42
40
  super
43
41
  end
44
42
 
45
- def validate_attributes
46
- return false if grant&.uses_pkce? && code_verifier.blank?
47
- return false if grant && !grant.pkce_supported? && !code_verifier.blank?
43
+ def validate_pkce_support
44
+ @invalid_request_reason = :not_support_pkce if grant &&
45
+ !grant.pkce_supported? &&
46
+ code_verifier.present?
47
+
48
+ @invalid_request_reason.nil?
49
+ end
50
+
51
+ def validate_params
52
+ @missing_param = if grant&.uses_pkce? && code_verifier.blank?
53
+ :code_verifier
54
+ elsif redirect_uri.blank?
55
+ :redirect_uri
56
+ end
48
57
 
49
- redirect_uri.present?
58
+ @missing_param.nil?
50
59
  end
51
60
 
52
61
  def validate_client
@@ -15,6 +15,8 @@ module Doorkeeper
15
15
  @response = TokenResponse.new(access_token)
16
16
  after_successful_response
17
17
  @response
18
+ elsif error == :invalid_request
19
+ @response = InvalidRequestResponse.from_request(self)
18
20
  else
19
21
  @response = ErrorResponse.from_request(self)
20
22
  end
@@ -5,11 +5,25 @@ module Doorkeeper
5
5
  class ClientCredentialsRequest < BaseRequest
6
6
  class Creator
7
7
  def call(client, scopes, attributes = {})
8
+ existing_token = existing_token_for(client, scopes)
9
+
10
+ if Doorkeeper.configuration.reuse_access_token && existing_token&.reusable?
11
+ return existing_token
12
+ end
13
+
14
+ existing_token&.revoke
15
+
8
16
  AccessToken.find_or_create_for(
9
17
  client, nil, scopes, attributes[:expires_in],
10
18
  attributes[:use_refresh_token]
11
19
  )
12
20
  end
21
+
22
+ private
23
+
24
+ def existing_token_for(client, scopes)
25
+ Doorkeeper::AccessToken.matching_token_for client, nil, scopes
26
+ end
13
27
  end
14
28
  end
15
29
  end