doorkeeper 5.0.2 → 5.1.0.rc1

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 (63) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +7 -3
  3. data/Dangerfile +5 -2
  4. data/Gemfile +3 -1
  5. data/NEWS.md +18 -10
  6. data/README.md +1 -1
  7. data/app/controllers/doorkeeper/tokens_controller.rb +6 -6
  8. data/app/views/doorkeeper/applications/show.html.erb +1 -1
  9. data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
  10. data/bin/console +15 -0
  11. data/gemfiles/rails_4_2.gemfile +1 -0
  12. data/gemfiles/rails_5_0.gemfile +1 -0
  13. data/gemfiles/rails_5_1.gemfile +1 -0
  14. data/gemfiles/rails_5_2.gemfile +2 -1
  15. data/gemfiles/rails_master.gemfile +1 -0
  16. data/lib/doorkeeper/config.rb +73 -6
  17. data/lib/doorkeeper/helpers/controller.rb +3 -2
  18. data/lib/doorkeeper/models/access_grant_mixin.rb +8 -1
  19. data/lib/doorkeeper/models/access_token_mixin.rb +40 -9
  20. data/lib/doorkeeper/models/application_mixin.rb +52 -1
  21. data/lib/doorkeeper/models/concerns/hashable.rb +137 -0
  22. data/lib/doorkeeper/models/concerns/scopes.rb +1 -1
  23. data/lib/doorkeeper/oauth/authorization/code.rb +1 -1
  24. data/lib/doorkeeper/oauth/authorization/token.rb +1 -1
  25. data/lib/doorkeeper/oauth/authorization_code_request.rb +1 -1
  26. data/lib/doorkeeper/oauth/client.rb +1 -1
  27. data/lib/doorkeeper/oauth/client_credentials/validation.rb +4 -3
  28. data/lib/doorkeeper/oauth/code_response.rb +2 -2
  29. data/lib/doorkeeper/oauth/helpers/scope_checker.rb +23 -8
  30. data/lib/doorkeeper/oauth/helpers/uri_checker.rb +32 -0
  31. data/lib/doorkeeper/oauth/password_access_token_request.rb +7 -2
  32. data/lib/doorkeeper/oauth/pre_authorization.rb +8 -3
  33. data/lib/doorkeeper/oauth/refresh_token_request.rb +4 -1
  34. data/lib/doorkeeper/oauth/token_response.rb +2 -2
  35. data/lib/doorkeeper/orm/active_record/access_grant.rb +22 -2
  36. data/lib/doorkeeper/orm/active_record/application.rb +16 -2
  37. data/lib/doorkeeper/version.rb +3 -3
  38. data/lib/doorkeeper.rb +1 -0
  39. data/lib/generators/doorkeeper/templates/initializer.rb +41 -1
  40. data/spec/controllers/application_metal_controller_spec.rb +18 -4
  41. data/spec/controllers/tokens_controller_spec.rb +7 -11
  42. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  43. data/spec/factories.rb +3 -3
  44. data/spec/lib/config_spec.rb +84 -0
  45. data/spec/lib/models/hashable_spec.rb +183 -0
  46. data/spec/lib/oauth/base_request_spec.rb +7 -7
  47. data/spec/lib/oauth/client_credentials/validation_spec.rb +3 -0
  48. data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
  49. data/spec/lib/oauth/helpers/uri_checker_spec.rb +20 -2
  50. data/spec/lib/oauth/password_access_token_request_spec.rb +32 -11
  51. data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
  52. data/spec/lib/oauth/token_response_spec.rb +13 -13
  53. data/spec/lib/oauth/token_spec.rb +14 -0
  54. data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
  55. data/spec/models/doorkeeper/access_token_spec.rb +123 -0
  56. data/spec/models/doorkeeper/application_spec.rb +37 -0
  57. data/spec/requests/flows/authorization_code_spec.rb +40 -0
  58. data/spec/requests/flows/password_spec.rb +4 -2
  59. data/spec/requests/flows/revoke_token_spec.rb +14 -30
  60. data/spec/spec_helper.rb +2 -1
  61. data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
  62. data/spec/support/shared/hashing_shared_context.rb +29 -0
  63. metadata +13 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3a0fa2d314b8aa479233379028c00d8107830f46
4
- data.tar.gz: c0bb3f5658cbbc4f7b640e08ae418afab36d7225
2
+ SHA256:
3
+ metadata.gz: 5773ab2b97881fdf1bb6fa66a4e176299d58c536f354ea9e9f4f7bb6dc0dcdea
4
+ data.tar.gz: 69ac6937d7eb7786a8c9ac331f540ef5aa1321b7c0624a0778c7437151764569
5
5
  SHA512:
6
- metadata.gz: 45411b41e6ca7e5b3016b6831efa7cb6df93dcca172ac1902a8561ca004f6701ebaf4364d5e12d439bd0867533aa4b98133224482cd91ce015b835b26753dc64
7
- data.tar.gz: 89aaf99254db0422ce86ed21f6e604ce8ecac6eb6c58070170b88be0fa02c338b71e7683d95f557319d75936e53f464e47118fbc85c35ac8a97cfd1588202622
6
+ metadata.gz: 5e4b8470847ab8e642d50d27972838c0b5181b3c97c4a25afca18861b3878e513346f7b72d5c5d6c31b5d8513d90c1e6426e0cfcfce51a06b1e04e563ae09ab0
7
+ data.tar.gz: 79c4363faf9a3f41bc639ee6e6ec756efbd99a96e4670e788bc4201b6f69eb992ffc95f2f56e3efbcd5645f2ed76e2c8d29f6e9807ffb6841becd31d868c6963
data/.travis.yml CHANGED
@@ -1,5 +1,5 @@
1
- cache: bundler
2
1
  language: ruby
2
+ cache: bundler
3
3
  sudo: false
4
4
 
5
5
  rvm:
@@ -8,10 +8,12 @@ rvm:
8
8
  - 2.3
9
9
  - 2.4
10
10
  - 2.5
11
- - ruby-2.6.0-preview1
11
+ - 2.6
12
+ - ruby-head
12
13
 
13
14
  before_install:
14
- - gem update --system # Need for Ruby 2.5.0. https://github.com/travis-ci/travis-ci/issues/8978
15
+ - "find /home/travis/.rvm/rubies -wholename '*default/bundler-*.gemspec' -delete"
16
+ - rvm @global do gem uninstall bundler -a -x -I || true
15
17
  - gem install bundler -v '~> 1.10'
16
18
 
17
19
  gemfile:
@@ -41,5 +43,7 @@ matrix:
41
43
  rvm: 2.2
42
44
  - gemfile: gemfiles/rails_master.gemfile
43
45
  rvm: 2.3
46
+ - gemfile: gemfiles/rails_master.gemfile
47
+ rvm: 2.4
44
48
  allow_failures:
45
49
  - gemfile: gemfiles/rails_master.gemfile
data/Dangerfile CHANGED
@@ -17,7 +17,7 @@ end
17
17
  # --------------------------------------------------------------------------------------------------------------------
18
18
  # Has any changes happened inside the actual library code?
19
19
  # --------------------------------------------------------------------------------------------------------------------
20
- has_app_changes = !git.modified_files.grep(/lib/).empty?
20
+ has_app_changes = !git.modified_files.grep(/lib|app/).empty?
21
21
  has_spec_changes = !git.modified_files.grep(/spec/).empty?
22
22
 
23
23
  # --------------------------------------------------------------------------------------------------------------------
@@ -52,7 +52,10 @@ Here's an example of a #{CHANGELOG_FILE} entry:
52
52
  ```
53
53
  MARKDOWN
54
54
 
55
- fail("Please include a changelog entry. \nYou can find it at [#{CHANGELOG_FILE}](#{GITHUB_REPO}/blob/master/#{CHANGELOG_FILE}).")
55
+ warn(
56
+ "Please include a changelog entry. \nYou can find it at [#{CHANGELOG_FILE}](#{GITHUB_REPO}/blob/master/#{CHANGELOG_FILE})." +
57
+ "You can skip this warning only if you made some typo fix or other small changes that didn't affect the API."
58
+ )
56
59
  end
57
60
 
58
61
  if git.commits.any? { |commit| commit.message =~ /^Merge branch '#{github.branch_for_base}'/ }
data/Gemfile CHANGED
@@ -1,9 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gem "rails", "~> 5.2"
3
+ gem "rails", ">= 5.2.1.1", "< 6.0"
4
4
 
5
5
  gem "appraisal"
6
6
 
7
+ gem "bcrypt", "~> 3.1"
8
+
7
9
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
8
10
  gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
9
11
  gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw]
data/NEWS.md CHANGED
@@ -7,7 +7,15 @@ User-visible changes worth mentioning.
7
7
 
8
8
  ## master
9
9
 
10
- - [#] Add your description here.
10
+ - [#1188] Use `params` instead of `request.POST` in tokens controller (fixes #1183).
11
+ - [#1179] Authorization Code Grant Flow without client id returns invalid_client error.
12
+ - [#1182] Fix loopback IP redirect URIs to conform with RFC8252, p. 7.3 (fixes #1170).
13
+ - [#1177] Allow to limit `scopes` for certain `grant_types`
14
+ - [#1162] Fix `enforce_content_type` for requests without body.
15
+ - [#1164] Fix error when `root_path` is not defined.
16
+ - [#1175] Internal refactor: use `scopes_string` inside `scopes`.
17
+ - [#1176] Fix test factory support for `factory_bot_rails`
18
+ - [#1168]: Allow optional hashing of tokens and secrets.
11
19
 
12
20
  ## 5.0.2
13
21
 
@@ -58,12 +66,12 @@ User-visible changes worth mentioning.
58
66
  `Doorkeeper#installed?` method
59
67
  - [#1031] Allow public clients to authenticate without `client_secret`. Define an app as
60
68
  either public or private/confidential
61
-
69
+
62
70
  **[IMPORTANT]**: all the applications (clients) now are considered as private by default.
63
71
  You need to manually change `confidential` column to `false` if you are using public clients,
64
72
  in other case your mobile (or other) applications will not be able to authorize.
65
73
  See [#1142](https://github.com/doorkeeper-gem/doorkeeper/issues/1142) for more details.
66
-
74
+
67
75
  - [#1010] Add configuration to enforce configured scopes (`default_scopes` and
68
76
  `optional_scopes`) for applications
69
77
  - [#1060] Ensure that the native redirect_uri parameter matches with redirect_uri of the client
@@ -81,26 +89,26 @@ User-visible changes worth mentioning.
81
89
  - [#1076] Add config to enforce content type to application/x-www-form-urlencoded
82
90
  - Fix bug with `force_ssl_in_redirect_uri` when it breaks existing applications with an
83
91
  SSL redirect_uri.
84
-
92
+
85
93
  ## 4.4.3
86
-
94
+
87
95
  - [#1143] Adds a config option `opt_out_native_route_change` to opt out of the breaking api
88
96
  changed introduced in https://github.com/doorkeeper-gem/doorkeeper/pull/1003
89
97
 
90
-
98
+
91
99
  ## 4.4.2
92
100
 
93
101
  - [#1130] Backport fix for native redirect_uri from 5.x.
94
-
102
+
95
103
  ## 4.4.1
96
104
 
97
105
  - [#1127] Backport token type to comply with the RFC6750 specification.
98
106
  - [#1125] Backport Quote surround I18n yes/no keys
99
-
107
+
100
108
  ## 4.4.0
101
-
109
+
102
110
  - [#1120] Backport security fix from 5.x for token revocation when using public clients
103
-
111
+
104
112
  **[IMPORTANT]**: all the applications (clients) now are considered as private by default.
105
113
  You need to manually change `confidential` column to `false` if you are using public clients,
106
114
  in other case your mobile (or other) applications will not be able to authorize.
data/README.md CHANGED
@@ -117,7 +117,7 @@ migration:
117
117
  [PKCE flow]: https://tools.ietf.org/html/rfc7636
118
118
 
119
119
  ```sh
120
- rails generate doorkeeper:pkce
120
+ rails generate doorkeeper:pkce
121
121
  ```
122
122
 
123
123
  Then run migrations:
@@ -4,11 +4,11 @@ module Doorkeeper
4
4
  class TokensController < Doorkeeper::ApplicationMetalController
5
5
  def create
6
6
  response = authorize_response
7
- headers.merge! response.headers
7
+ headers.merge!(response.headers)
8
8
  self.response_body = response.body.to_json
9
9
  self.status = response.status
10
- rescue Errors::DoorkeeperError => e
11
- handle_token_exception e
10
+ rescue Errors::DoorkeeperError => error
11
+ handle_token_exception(error)
12
12
  end
13
13
 
14
14
  # OAuth 2.0 Token Revocation - http://tools.ietf.org/html/rfc7009
@@ -75,12 +75,12 @@ module Doorkeeper
75
75
  end
76
76
 
77
77
  def token
78
- @token ||= AccessToken.by_token(request.POST['token']) ||
79
- AccessToken.by_refresh_token(request.POST['token'])
78
+ @token ||= AccessToken.by_token(params['token']) ||
79
+ AccessToken.by_refresh_token(params['token'])
80
80
  end
81
81
 
82
82
  def strategy
83
- @strategy ||= server.token_request params[:grant_type]
83
+ @strategy ||= server.token_request(params[:grant_type])
84
84
  end
85
85
 
86
86
  def authorize_response
@@ -8,7 +8,7 @@
8
8
  <p><code class="bg-light" id="application_id"><%= @application.uid %></code></p>
9
9
 
10
10
  <h4><%= t('.secret') %>:</h4>
11
- <p><code class="bg-light" id="secret"><%= @application.secret %></code></p>
11
+ <p><code class="bg-light" id="secret"><%= @application.plaintext_secret %></code></p>
12
12
 
13
13
  <h4><%= t('.scopes') %>:</h4>
14
14
  <p><code class="bg-light" id="scopes"><%= @application.scopes.presence || raw('&nbsp;') %></code></p>
@@ -17,9 +17,11 @@
17
17
  <li class="nav-item <%= 'active' if request.path == oauth_applications_path %>">
18
18
  <%= link_to t('doorkeeper.layouts.admin.nav.applications'), oauth_applications_path, class: 'nav-link' %>
19
19
  </li>
20
- <li class="nav-item">
21
- <%= link_to t('doorkeeper.layouts.admin.nav.home'), root_path, class: 'nav-link' %>
22
- </li>
20
+ <% if respond_to?(:root_path) %>
21
+ <li class="nav-item">
22
+ <%= link_to t('doorkeeper.layouts.admin.nav.home'), root_path, class: 'nav-link' %>
23
+ </li>
24
+ <% end %>
23
25
  </ul>
24
26
  </div>
25
27
  </nav>
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'rails/all'
5
+ require 'doorkeeper'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 4.2.0"
6
+ gem "bcrypt", "~> 3.1"
6
7
  gem "appraisal"
7
8
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
8
9
  gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 5.0.0"
6
+ gem "bcrypt", "~> 3.1"
6
7
  gem "appraisal"
7
8
  gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
8
9
  gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
@@ -3,6 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 5.1.0"
6
+ gem "bcrypt", "~> 3.1"
6
7
  gem "appraisal"
7
8
  gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
8
9
  gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
@@ -2,7 +2,8 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
- gem "rails", "5.2.0"
5
+ gem "rails", "~> 5.2.0"
6
+ gem "bcrypt", "~> 3.1"
6
7
  gem "appraisal"
7
8
  gem "activerecord-jdbcsqlite3-adapter", platforms: :jruby
8
9
  gem "sqlite3", platforms: [:ruby, :mswin, :mingw, :x64_mingw]
@@ -6,6 +6,7 @@ gem "rails", git: 'https://github.com/rails/rails'
6
6
  gem "arel", git: 'https://github.com/rails/arel'
7
7
 
8
8
  gem "appraisal"
9
+ gem "bcrypt", "~> 3.1"
9
10
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby
10
11
  gem "sqlite3", platform: [:ruby, :mswin, :mingw, :x64_mingw]
11
12
  gem "tzinfo-data", platforms: [:mingw, :mswin, :x64_mingw]
@@ -46,6 +46,7 @@ module Doorkeeper
46
46
  end
47
47
 
48
48
  def build
49
+ @config.validate
49
50
  @config
50
51
  end
51
52
 
@@ -83,6 +84,13 @@ module Doorkeeper
83
84
  @config.instance_variable_set(:@optional_scopes, OAuth::Scopes.from_array(scopes))
84
85
  end
85
86
 
87
+ # Define scopes_by_grant_type to limit certain scope to certain grant_type
88
+ # @param { Hash } with grant_types as keys.
89
+ # Default set to {} i.e. no limitation on scopes usage
90
+ def scopes_by_grant_type(hash = {})
91
+ @config.instance_variable_set(:@scopes_by_grant_type, hash)
92
+ end
93
+
86
94
  # Change the way client credentials are retrieved from the request object.
87
95
  # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then
88
96
  # falls back to the `:client_id` and `:client_secret` params from the
@@ -136,6 +144,24 @@ module Doorkeeper
136
144
  def enforce_content_type
137
145
  @config.instance_variable_set(:@enforce_content_type, true)
138
146
  end
147
+
148
+ # Allow optional hashing of input tokens before persisting them.
149
+ # Will be used for hashing of input token and grants.
150
+ def hash_token_secrets
151
+ @config.instance_variable_set(:@hash_token_secrets, true)
152
+ end
153
+
154
+ # Allow optional hashing of application secrets before persisting them.
155
+ # Will be used for hashing of input token and grants.
156
+ def hash_application_secrets
157
+ @config.instance_variable_set(:@hash_application_secrets, true)
158
+ end
159
+
160
+ # Allow plain value lookup when using +hash_token_secrets+
161
+ # or +hash_application_secrets+ to avoid disrupting application experience
162
+ def fallback_to_plain_secrets
163
+ @config.instance_variable_set(:@fallback_to_plain_secrets, true)
164
+ end
139
165
  end
140
166
 
141
167
  module Option
@@ -286,9 +312,14 @@ module Doorkeeper
286
312
  option :base_controller,
287
313
  default: 'ActionController::Base'
288
314
 
289
- attr_reader :reuse_access_token
290
- attr_reader :api_only
291
- attr_reader :enforce_content_type
315
+ attr_reader :api_only,
316
+ :enforce_content_type,
317
+ :reuse_access_token
318
+
319
+ # Return the valid subset of this configuration
320
+ def validate
321
+ validate_reuse_access_token_value
322
+ end
292
323
 
293
324
  def api_only
294
325
  @api_only ||= false
@@ -307,21 +338,33 @@ module Doorkeeper
307
338
  end
308
339
 
309
340
  def enforce_configured_scopes?
310
- !!(defined?(@enforce_configured_scopes) && @enforce_configured_scopes)
341
+ option_set? :enforce_configured_scopes
311
342
  end
312
343
 
313
344
  def enable_application_owner?
314
- !!(defined?(@enable_application_owner) && @enable_application_owner)
345
+ option_set? :enable_application_owner
315
346
  end
316
347
 
317
348
  def confirm_application_owner?
318
- !!(defined?(@confirm_application_owner) && @confirm_application_owner)
349
+ option_set? :confirm_application_owner
319
350
  end
320
351
 
321
352
  def raise_on_errors?
322
353
  handle_auth_errors == :raise
323
354
  end
324
355
 
356
+ def hash_token_secrets?
357
+ option_set? :hash_token_secrets
358
+ end
359
+
360
+ def hash_application_secrets?
361
+ option_set? :hash_application_secrets
362
+ end
363
+
364
+ def fallback_to_plain_secrets?
365
+ option_set? :fallback_to_plain_secrets
366
+ end
367
+
325
368
  def default_scopes
326
369
  @default_scopes ||= OAuth::Scopes.new
327
370
  end
@@ -334,6 +377,10 @@ module Doorkeeper
334
377
  @scopes ||= default_scopes + optional_scopes
335
378
  end
336
379
 
380
+ def scopes_by_grant_type
381
+ @scopes_by_grant_type ||= {}
382
+ end
383
+
337
384
  def client_credentials_methods
338
385
  @client_credentials_methods ||= %i[from_basic from_params]
339
386
  end
@@ -352,6 +399,12 @@ module Doorkeeper
352
399
 
353
400
  private
354
401
 
402
+ # Helper to read boolearized configuration option
403
+ def option_set?(instance_key)
404
+ var = instance_variable_get("@#{instance_key}")
405
+ !!(defined?(var) && var)
406
+ end
407
+
355
408
  # Determines what values are acceptable for 'response_type' param in
356
409
  # authorization request endpoint, and return them as an array of strings.
357
410
  #
@@ -370,5 +423,19 @@ module Doorkeeper
370
423
  types << 'refresh_token' if refresh_token_enabled?
371
424
  types
372
425
  end
426
+
427
+ # Determine whether +reuse_access_token+ and +hash_token_secrets+
428
+ # have both been activated.
429
+ #
430
+ # In that case, disable reuse_access_token value and warn the user.
431
+ def validate_reuse_access_token_value
432
+ return unless hash_token_secrets? && reuse_access_token
433
+
434
+ ::Rails.logger.warn(
435
+ 'You are configured both reuse_access_token AND hash_token_secrets. ' \
436
+ 'This combination is unsupported. reuse_access_token will be disabled'
437
+ )
438
+ @reuse_access_token = false
439
+ end
373
440
  end
374
441
  end
@@ -55,8 +55,9 @@ module Doorkeeper
55
55
  end
56
56
 
57
57
  def enforce_content_type
58
- return if request.content_type == 'application/x-www-form-urlencoded'
59
- render json: {}, status: :unsupported_media_type
58
+ if (request.put? || request.post? || request.patch?) && request.content_type != 'application/x-www-form-urlencoded'
59
+ render json: {}, status: :unsupported_media_type
60
+ end
60
61
  end
61
62
  end
62
63
  end
@@ -9,6 +9,7 @@ module Doorkeeper
9
9
  include Models::Revocable
10
10
  include Models::Accessible
11
11
  include Models::Orderable
12
+ include Models::Hashable
12
13
  include Models::Scopes
13
14
 
14
15
  # never uses pkce, if pkce migrations were not generated
@@ -30,7 +31,13 @@ module Doorkeeper
30
31
  # if there is no record with such token
31
32
  #
32
33
  def by_token(token)
33
- find_by(token: token.to_s)
34
+ find_by_plaintext_token(:token, token)
35
+ end
36
+
37
+ # We want to perform secret hashing whenever the user
38
+ # enables the configuration option +hash_token_secrets+
39
+ def perform_secret_hashing?
40
+ Doorkeeper.configuration.hash_token_secrets?
34
41
  end
35
42
 
36
43
  # Revokes AccessGrant records that have not been revoked and associated
@@ -9,20 +9,21 @@ module Doorkeeper
9
9
  include Models::Revocable
10
10
  include Models::Accessible
11
11
  include Models::Orderable
12
+ include Models::Hashable
12
13
  include Models::Scopes
13
14
 
14
15
  module ClassMethods
15
16
  # Returns an instance of the Doorkeeper::AccessToken with
16
- # specific token value.
17
+ # specific plain text token value.
17
18
  #
18
19
  # @param token [#to_s]
19
- # token value (any object that responds to `#to_s`)
20
+ # Plain text token value (any object that responds to `#to_s`)
20
21
  #
21
22
  # @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
22
23
  # if there is no record with such token
23
24
  #
24
25
  def by_token(token)
25
- find_by(token: token.to_s)
26
+ find_by_plaintext_token(:token, token)
26
27
  end
27
28
 
28
29
  # Returns an instance of the Doorkeeper::AccessToken
@@ -35,7 +36,13 @@ module Doorkeeper
35
36
  # if there is no record with such refresh token
36
37
  #
37
38
  def by_refresh_token(refresh_token)
38
- find_by(refresh_token: refresh_token.to_s)
39
+ find_by_plaintext_token(:refresh_token, refresh_token)
40
+ end
41
+
42
+ # We want to perform secret hashing whenever the user
43
+ # enables the configuration option +hash_token_secrets+
44
+ def perform_secret_hashing?
45
+ Doorkeeper.configuration.hash_token_secrets?
39
46
  end
40
47
 
41
48
  # Revokes AccessToken records that have not been revoked and associated
@@ -98,9 +105,9 @@ module Doorkeeper
98
105
 
99
106
  (token_scopes.sort == param_scopes.sort) &&
100
107
  Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
101
- param_scopes.to_s,
102
- Doorkeeper.configuration.scopes,
103
- app_scopes
108
+ scope_str: param_scopes.to_s,
109
+ server_scopes: Doorkeeper.configuration.scopes,
110
+ app_scopes: app_scopes
104
111
  )
105
112
  end
106
113
 
@@ -219,6 +226,26 @@ module Doorkeeper
219
226
  accessible? && includes_scope?(*scopes)
220
227
  end
221
228
 
229
+ # We keep a volatile copy of the raw refresh token for initial communication
230
+ # The stored refresh_token may be mapped and not available in cleartext.
231
+ def plaintext_refresh_token
232
+ if perform_secret_hashing?
233
+ @raw_refresh_token
234
+ else
235
+ refresh_token
236
+ end
237
+ end
238
+
239
+ # We keep a volatile copy of the raw token for initial communication
240
+ # The stored refresh_token may be mapped and not available in cleartext.
241
+ def plaintext_token
242
+ if perform_secret_hashing?
243
+ @raw_token
244
+ else
245
+ token
246
+ end
247
+ end
248
+
222
249
  private
223
250
 
224
251
  # Generates refresh token with UniqueToken generator.
@@ -226,7 +253,8 @@ module Doorkeeper
226
253
  # @return [String] refresh token value
227
254
  #
228
255
  def generate_refresh_token
229
- self.refresh_token = UniqueToken.generate
256
+ @raw_refresh_token = UniqueToken.generate
257
+ self.refresh_token = hashed_or_plain_token(@raw_refresh_token)
230
258
  end
231
259
 
232
260
  # Generates and sets the token value with the
@@ -242,13 +270,16 @@ module Doorkeeper
242
270
  def generate_token
243
271
  self.created_at ||= Time.now.utc
244
272
 
245
- self.token = token_generator.generate(
273
+ @raw_token = token_generator.generate(
246
274
  resource_owner_id: resource_owner_id,
247
275
  scopes: scopes,
248
276
  application: application,
249
277
  expires_in: expires_in,
250
278
  created_at: created_at
251
279
  )
280
+
281
+ self.token = hashed_or_plain_token(@raw_token)
282
+ @raw_token
252
283
  end
253
284
 
254
285
  def token_generator
@@ -1,14 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bcrypt'
4
+
3
5
  module Doorkeeper
4
6
  module ApplicationMixin
5
7
  extend ActiveSupport::Concern
6
8
 
7
9
  include OAuth::Helpers
8
10
  include Models::Orderable
11
+ include Models::Hashable
9
12
  include Models::Scopes
10
13
 
14
+ included do
15
+ # Use BCrypt as the hashing function for applications
16
+ self.secret_hash_function = lambda do |plain_token|
17
+ BCrypt::Password.create(plain_token.to_s)
18
+ end
19
+
20
+ # Also need to override the comparer function for BCrypt
21
+ self.secret_comparer = lambda do |plain, secret|
22
+ begin
23
+ BCrypt::Password.new(secret.to_s) == plain.to_s
24
+ rescue BCrypt::Errors::InvalidHash
25
+ false
26
+ end
27
+ end
28
+ end
29
+
30
+ # :nodoc
11
31
  module ClassMethods
32
+ # We want to perform secret hashing whenever the user
33
+ # enables the configuration option +hash_application_secrets+
34
+ def perform_secret_hashing?
35
+ Doorkeeper.configuration.hash_application_secrets?
36
+ end
37
+
12
38
  # Returns an instance of the Doorkeeper::Application with
13
39
  # specific UID and secret.
14
40
  #
@@ -25,7 +51,7 @@ module Doorkeeper
25
51
  app = by_uid(uid)
26
52
  return unless app
27
53
  return app if secret.blank? && !app.confidential?
28
- return unless app.secret == secret
54
+ return unless app.secret_matches?(secret)
29
55
  app
30
56
  end
31
57
 
@@ -49,5 +75,30 @@ module Doorkeeper
49
75
  def redirect_uri=(uris)
50
76
  super(uris.is_a?(Array) ? uris.join("\n") : uris)
51
77
  end
78
+
79
+ # Check whether the given plain text secret matches our stored secret
80
+ #
81
+ # @param input [#to_s] Plain secret provided by user
82
+ # (any object that responds to `#to_s`)
83
+ #
84
+ # @return [true] Whether the given secret matches the stored secret
85
+ # of this application.
86
+ #
87
+ def secret_matches?(input)
88
+ # return false if either is nil, since secure_compare depends on strings
89
+ # but Application secrets MAY be nil depending on confidentiality.
90
+ return false if input.nil? || secret.nil?
91
+
92
+ # When matching the secret by comparer function, all is well.
93
+ return true if self.class.secret_matches?(input, secret)
94
+
95
+ # When fallback lookup is enabled, ensure applications
96
+ # with plain secrets can still be found
97
+ if Doorkeeper.configuration.fallback_to_plain_secrets?
98
+ ActiveSupport::SecurityUtils.secure_compare(input, secret)
99
+ else
100
+ false
101
+ end
102
+ end
52
103
  end
53
104
  end