doorkeeper 5.0.0 → 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.
- checksums.yaml +5 -5
- data/.travis.yml +12 -3
- data/Dangerfile +67 -0
- data/Gemfile +3 -1
- data/NEWS.md +49 -5
- data/README.md +19 -4
- data/app/controllers/doorkeeper/application_controller.rb +2 -0
- data/app/controllers/doorkeeper/application_metal_controller.rb +2 -0
- data/app/controllers/doorkeeper/applications_controller.rb +4 -2
- data/app/controllers/doorkeeper/authorizations_controller.rb +3 -3
- data/app/controllers/doorkeeper/authorized_applications_controller.rb +2 -0
- data/app/controllers/doorkeeper/token_info_controller.rb +2 -0
- data/app/controllers/doorkeeper/tokens_controller.rb +8 -6
- data/app/helpers/doorkeeper/dashboard_helper.rb +2 -0
- data/app/validators/redirect_uri_validator.rb +2 -0
- data/app/views/doorkeeper/applications/show.html.erb +1 -1
- data/app/views/layouts/doorkeeper/admin.html.erb +5 -3
- data/bin/console +15 -0
- data/doorkeeper.gemspec +23 -22
- data/gemfiles/rails_4_2.gemfile +1 -0
- data/gemfiles/rails_5_0.gemfile +1 -0
- data/gemfiles/rails_5_1.gemfile +1 -0
- data/gemfiles/rails_5_2.gemfile +2 -1
- data/gemfiles/rails_master.gemfile +1 -0
- data/lib/doorkeeper/config.rb +80 -8
- data/lib/doorkeeper/engine.rb +2 -0
- data/lib/doorkeeper/errors.rb +17 -0
- data/lib/doorkeeper/grape/authorization_decorator.rb +2 -0
- data/lib/doorkeeper/grape/helpers.rb +2 -0
- data/lib/doorkeeper/helpers/controller.rb +5 -2
- data/lib/doorkeeper/models/access_grant_mixin.rb +13 -4
- data/lib/doorkeeper/models/access_token_mixin.rb +45 -12
- data/lib/doorkeeper/models/application_mixin.rb +54 -1
- data/lib/doorkeeper/models/concerns/accessible.rb +2 -0
- data/lib/doorkeeper/models/concerns/expirable.rb +2 -0
- data/lib/doorkeeper/models/concerns/hashable.rb +137 -0
- data/lib/doorkeeper/models/concerns/orderable.rb +2 -0
- data/lib/doorkeeper/models/concerns/ownership.rb +2 -0
- data/lib/doorkeeper/models/concerns/revocable.rb +2 -0
- data/lib/doorkeeper/models/concerns/scopes.rb +3 -1
- data/lib/doorkeeper/oauth/authorization/code.rb +3 -1
- data/lib/doorkeeper/oauth/authorization/context.rb +2 -0
- data/lib/doorkeeper/oauth/authorization/token.rb +3 -1
- data/lib/doorkeeper/oauth/authorization/uri_builder.rb +2 -0
- data/lib/doorkeeper/oauth/authorization_code_request.rb +3 -1
- data/lib/doorkeeper/oauth/base_request.rb +2 -0
- data/lib/doorkeeper/oauth/base_response.rb +2 -0
- data/lib/doorkeeper/oauth/client/credentials.rb +2 -0
- data/lib/doorkeeper/oauth/client.rb +3 -1
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +2 -0
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +2 -0
- data/lib/doorkeeper/oauth/client_credentials/validation.rb +6 -3
- data/lib/doorkeeper/oauth/client_credentials_request.rb +2 -0
- data/lib/doorkeeper/oauth/code_request.rb +2 -0
- data/lib/doorkeeper/oauth/code_response.rb +4 -2
- data/lib/doorkeeper/oauth/error.rb +2 -0
- data/lib/doorkeeper/oauth/error_response.rb +10 -0
- data/lib/doorkeeper/oauth/forbidden_token_response.rb +9 -2
- data/lib/doorkeeper/oauth/helpers/scope_checker.rb +25 -8
- data/lib/doorkeeper/oauth/helpers/unique_token.rb +2 -0
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +37 -2
- data/lib/doorkeeper/oauth/invalid_token_response.rb +18 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +9 -2
- data/lib/doorkeeper/oauth/pre_authorization.rb +10 -3
- data/lib/doorkeeper/oauth/refresh_token_request.rb +14 -3
- data/lib/doorkeeper/oauth/scopes.rb +2 -0
- data/lib/doorkeeper/oauth/token.rb +2 -0
- data/lib/doorkeeper/oauth/token_introspection.rb +2 -0
- data/lib/doorkeeper/oauth/token_request.rb +2 -0
- data/lib/doorkeeper/oauth/token_response.rb +4 -2
- data/lib/doorkeeper/orm/active_record/access_grant.rb +22 -2
- data/lib/doorkeeper/orm/active_record/application.rb +16 -2
- data/lib/doorkeeper/orm/active_record.rb +2 -0
- data/lib/doorkeeper/rails/helpers.rb +4 -0
- data/lib/doorkeeper/rails/routes/mapper.rb +2 -0
- data/lib/doorkeeper/rails/routes/mapping.rb +2 -0
- data/lib/doorkeeper/rails/routes.rb +9 -2
- data/lib/doorkeeper/rake/db.rake +4 -4
- data/lib/doorkeeper/request/authorization_code.rb +2 -0
- data/lib/doorkeeper/request/client_credentials.rb +2 -0
- data/lib/doorkeeper/request/code.rb +2 -0
- data/lib/doorkeeper/request/password.rb +2 -0
- data/lib/doorkeeper/request/refresh_token.rb +2 -0
- data/lib/doorkeeper/request/strategy.rb +2 -0
- data/lib/doorkeeper/request/token.rb +2 -0
- data/lib/doorkeeper/request.rb +2 -0
- data/lib/doorkeeper/server.rb +2 -0
- data/lib/doorkeeper/stale_records_cleaner.rb +20 -0
- data/lib/doorkeeper/validations.rb +2 -0
- data/lib/doorkeeper/version.rb +4 -2
- data/lib/doorkeeper.rb +2 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +61 -3
- data/lib/generators/doorkeeper/templates/migration.rb.erb +2 -3
- data/spec/controllers/application_metal_controller_spec.rb +18 -4
- data/spec/controllers/applications_controller_spec.rb +37 -41
- data/spec/controllers/authorizations_controller_spec.rb +71 -18
- data/spec/controllers/protected_resources_controller_spec.rb +44 -2
- data/spec/controllers/tokens_controller_spec.rb +11 -16
- data/spec/dummy/Rakefile +1 -1
- data/spec/dummy/app/controllers/application_controller.rb +1 -1
- data/spec/dummy/app/controllers/custom_authorizations_controller.rb +1 -1
- data/spec/dummy/app/controllers/home_controller.rb +1 -2
- data/spec/dummy/config/application.rb +1 -1
- data/spec/dummy/config/boot.rb +2 -4
- data/spec/dummy/config/environment.rb +1 -1
- data/spec/dummy/config/environments/test.rb +1 -1
- data/spec/dummy/config/initializers/doorkeeper.rb +2 -1
- data/spec/dummy/config/initializers/new_framework_defaults.rb +1 -3
- data/spec/dummy/config/initializers/secret_token.rb +1 -1
- data/spec/dummy/config.ru +1 -1
- data/spec/dummy/db/migrate/20151223192035_create_doorkeeper_tables.rb +4 -4
- data/spec/dummy/db/migrate/20151223200000_add_owner_to_application.rb +1 -1
- data/spec/dummy/script/rails +4 -3
- data/spec/factories.rb +9 -9
- data/spec/generators/install_generator_spec.rb +4 -1
- data/spec/generators/templates/routes.rb +0 -1
- data/spec/generators/views_generator_spec.rb +1 -1
- data/spec/grape/grape_integration_spec.rb +1 -1
- data/spec/lib/config_spec.rb +109 -8
- data/spec/lib/doorkeeper_spec.rb +5 -5
- data/spec/lib/models/hashable_spec.rb +183 -0
- data/spec/lib/oauth/authorization_code_request_spec.rb +9 -6
- data/spec/lib/oauth/base_request_spec.rb +10 -10
- data/spec/lib/oauth/client/credentials_spec.rb +2 -2
- data/spec/lib/oauth/client_credentials/issuer_spec.rb +0 -2
- data/spec/lib/oauth/client_credentials/validation_spec.rb +5 -1
- data/spec/lib/oauth/client_credentials_integration_spec.rb +1 -1
- data/spec/lib/oauth/code_request_spec.rb +2 -2
- data/spec/lib/oauth/code_response_spec.rb +1 -1
- data/spec/lib/oauth/helpers/scope_checker_spec.rb +52 -17
- data/spec/lib/oauth/helpers/uri_checker_spec.rb +37 -8
- data/spec/lib/oauth/password_access_token_request_spec.rb +48 -15
- data/spec/lib/oauth/pre_authorization_spec.rb +24 -0
- data/spec/lib/oauth/refresh_token_request_spec.rb +11 -7
- data/spec/lib/oauth/token_request_spec.rb +5 -5
- data/spec/lib/oauth/token_response_spec.rb +13 -13
- data/spec/lib/oauth/token_spec.rb +18 -1
- data/spec/lib/server_spec.rb +6 -6
- data/spec/lib/{orm/active_record/stale_records_cleaner_spec.rb → stale_records_cleaner_spec.rb} +14 -4
- data/spec/models/doorkeeper/access_grant_spec.rb +61 -0
- data/spec/models/doorkeeper/access_token_spec.rb +137 -10
- data/spec/models/doorkeeper/application_spec.rb +41 -4
- data/spec/requests/applications/applications_request_spec.rb +2 -2
- data/spec/requests/endpoints/authorization_spec.rb +2 -2
- data/spec/requests/flows/authorization_code_errors_spec.rb +1 -1
- data/spec/requests/flows/authorization_code_spec.rb +115 -15
- data/spec/requests/flows/implicit_grant_errors_spec.rb +2 -2
- data/spec/requests/flows/password_spec.rb +10 -4
- data/spec/requests/flows/refresh_token_spec.rb +57 -0
- data/spec/requests/flows/revoke_token_spec.rb +23 -39
- data/spec/requests/protected_resources/private_api_spec.rb +2 -2
- data/spec/spec_helper.rb +2 -1
- data/spec/support/doorkeeper_rspec.rb +2 -1
- data/spec/support/helpers/model_helper.rb +8 -4
- data/spec/support/helpers/url_helper.rb +11 -11
- data/spec/support/ruby_2_6_rails_4_2_patch.rb +14 -0
- data/spec/support/shared/controllers_shared_context.rb +56 -0
- data/spec/support/shared/hashing_shared_context.rb +29 -0
- data/spec/validators/redirect_uri_validator_spec.rb +2 -2
- metadata +31 -8
data/lib/doorkeeper/config.rb
CHANGED
|
@@ -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
|
|
@@ -107,7 +115,7 @@ module Doorkeeper
|
|
|
107
115
|
def use_refresh_token(enabled = true, &block)
|
|
108
116
|
@config.instance_variable_set(
|
|
109
117
|
:@refresh_token_enabled,
|
|
110
|
-
block
|
|
118
|
+
block || enabled
|
|
111
119
|
)
|
|
112
120
|
end
|
|
113
121
|
|
|
@@ -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
|
|
@@ -177,7 +203,7 @@ module Doorkeeper
|
|
|
177
203
|
value = if attribute_builder
|
|
178
204
|
attribute_builder.new(&block).build
|
|
179
205
|
else
|
|
180
|
-
block
|
|
206
|
+
block || args.first
|
|
181
207
|
end
|
|
182
208
|
|
|
183
209
|
@config.instance_variable_set(:"@#{attribute}", value)
|
|
@@ -239,6 +265,7 @@ module Doorkeeper
|
|
|
239
265
|
option :native_redirect_uri, default: 'urn:ietf:wg:oauth:2.0:oob'
|
|
240
266
|
option :active_record_options, default: {}
|
|
241
267
|
option :grant_flows, default: %w[authorization_code client_credentials]
|
|
268
|
+
option :handle_auth_errors, default: :render
|
|
242
269
|
|
|
243
270
|
# Allows to forbid specific Application redirect URI's by custom rules.
|
|
244
271
|
# Doesn't forbid any URI by default.
|
|
@@ -285,9 +312,14 @@ module Doorkeeper
|
|
|
285
312
|
option :base_controller,
|
|
286
313
|
default: 'ActionController::Base'
|
|
287
314
|
|
|
288
|
-
attr_reader :
|
|
289
|
-
|
|
290
|
-
|
|
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
|
|
291
323
|
|
|
292
324
|
def api_only
|
|
293
325
|
@api_only ||= false
|
|
@@ -306,15 +338,31 @@ module Doorkeeper
|
|
|
306
338
|
end
|
|
307
339
|
|
|
308
340
|
def enforce_configured_scopes?
|
|
309
|
-
|
|
341
|
+
option_set? :enforce_configured_scopes
|
|
310
342
|
end
|
|
311
343
|
|
|
312
344
|
def enable_application_owner?
|
|
313
|
-
|
|
345
|
+
option_set? :enable_application_owner
|
|
314
346
|
end
|
|
315
347
|
|
|
316
348
|
def confirm_application_owner?
|
|
317
|
-
|
|
349
|
+
option_set? :confirm_application_owner
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def raise_on_errors?
|
|
353
|
+
handle_auth_errors == :raise
|
|
354
|
+
end
|
|
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
|
|
318
366
|
end
|
|
319
367
|
|
|
320
368
|
def default_scopes
|
|
@@ -329,6 +377,10 @@ module Doorkeeper
|
|
|
329
377
|
@scopes ||= default_scopes + optional_scopes
|
|
330
378
|
end
|
|
331
379
|
|
|
380
|
+
def scopes_by_grant_type
|
|
381
|
+
@scopes_by_grant_type ||= {}
|
|
382
|
+
end
|
|
383
|
+
|
|
332
384
|
def client_credentials_methods
|
|
333
385
|
@client_credentials_methods ||= %i[from_basic from_params]
|
|
334
386
|
end
|
|
@@ -347,6 +399,12 @@ module Doorkeeper
|
|
|
347
399
|
|
|
348
400
|
private
|
|
349
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
|
+
|
|
350
408
|
# Determines what values are acceptable for 'response_type' param in
|
|
351
409
|
# authorization request endpoint, and return them as an array of strings.
|
|
352
410
|
#
|
|
@@ -365,5 +423,19 @@ module Doorkeeper
|
|
|
365
423
|
types << 'refresh_token' if refresh_token_enabled?
|
|
366
424
|
types
|
|
367
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
|
|
368
440
|
end
|
|
369
441
|
end
|
data/lib/doorkeeper/engine.rb
CHANGED
data/lib/doorkeeper/errors.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doorkeeper
|
|
2
4
|
module Errors
|
|
3
5
|
class DoorkeeperError < StandardError
|
|
@@ -36,7 +38,22 @@ module Doorkeeper
|
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
41
|
+
class BaseResponseError < DoorkeeperError
|
|
42
|
+
attr_reader :response
|
|
43
|
+
|
|
44
|
+
def initialize(response)
|
|
45
|
+
@response = response
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
39
49
|
UnableToGenerateToken = Class.new(DoorkeeperError)
|
|
40
50
|
TokenGeneratorNotFound = Class.new(DoorkeeperError)
|
|
51
|
+
NoOrmCleaner = Class.new(DoorkeeperError)
|
|
52
|
+
|
|
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
|
|
41
58
|
end
|
|
42
59
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Define methods that can be called in any controller that inherits from
|
|
2
4
|
# Doorkeeper::ApplicationMetalController or Doorkeeper::ApplicationController
|
|
3
5
|
module Doorkeeper
|
|
@@ -53,8 +55,9 @@ module Doorkeeper
|
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
def enforce_content_type
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doorkeeper
|
|
2
4
|
module AccessGrantMixin
|
|
3
5
|
extend ActiveSupport::Concern
|
|
@@ -7,6 +9,7 @@ module Doorkeeper
|
|
|
7
9
|
include Models::Revocable
|
|
8
10
|
include Models::Accessible
|
|
9
11
|
include Models::Orderable
|
|
12
|
+
include Models::Hashable
|
|
10
13
|
include Models::Scopes
|
|
11
14
|
|
|
12
15
|
# never uses pkce, if pkce migrations were not generated
|
|
@@ -28,7 +31,13 @@ module Doorkeeper
|
|
|
28
31
|
# if there is no record with such token
|
|
29
32
|
#
|
|
30
33
|
def by_token(token)
|
|
31
|
-
|
|
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?
|
|
32
41
|
end
|
|
33
42
|
|
|
34
43
|
# Revokes AccessGrant records that have not been revoked and associated
|
|
@@ -42,8 +51,8 @@ module Doorkeeper
|
|
|
42
51
|
def revoke_all_for(application_id, resource_owner, clock = Time)
|
|
43
52
|
where(application_id: application_id,
|
|
44
53
|
resource_owner_id: resource_owner.id,
|
|
45
|
-
revoked_at: nil)
|
|
46
|
-
update_all(revoked_at: clock.now.utc)
|
|
54
|
+
revoked_at: nil)
|
|
55
|
+
.update_all(revoked_at: clock.now.utc)
|
|
47
56
|
end
|
|
48
57
|
|
|
49
58
|
# Implements PKCE code_challenge encoding without base64 padding as described in the spec.
|
|
@@ -78,7 +87,7 @@ module Doorkeeper
|
|
|
78
87
|
#
|
|
79
88
|
# urlsafe_encode64(bin)
|
|
80
89
|
# Returns the Base64-encoded version of bin. This method complies with
|
|
81
|
-
#
|
|
90
|
+
# "Base 64 Encoding with URL and Filename Safe Alphabet" in RFC 4648.
|
|
82
91
|
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
|
|
83
92
|
|
|
84
93
|
# @param code_verifier [#to_s] a one time use value (any object that responds to `#to_s`)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Doorkeeper
|
|
2
4
|
module AccessTokenMixin
|
|
3
5
|
extend ActiveSupport::Concern
|
|
@@ -7,20 +9,21 @@ module Doorkeeper
|
|
|
7
9
|
include Models::Revocable
|
|
8
10
|
include Models::Accessible
|
|
9
11
|
include Models::Orderable
|
|
12
|
+
include Models::Hashable
|
|
10
13
|
include Models::Scopes
|
|
11
14
|
|
|
12
15
|
module ClassMethods
|
|
13
16
|
# Returns an instance of the Doorkeeper::AccessToken with
|
|
14
|
-
# specific token value.
|
|
17
|
+
# specific plain text token value.
|
|
15
18
|
#
|
|
16
19
|
# @param token [#to_s]
|
|
17
|
-
# token value (any object that responds to `#to_s`)
|
|
20
|
+
# Plain text token value (any object that responds to `#to_s`)
|
|
18
21
|
#
|
|
19
22
|
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
|
20
23
|
# if there is no record with such token
|
|
21
24
|
#
|
|
22
25
|
def by_token(token)
|
|
23
|
-
|
|
26
|
+
find_by_plaintext_token(:token, token)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
29
|
# Returns an instance of the Doorkeeper::AccessToken
|
|
@@ -33,7 +36,13 @@ module Doorkeeper
|
|
|
33
36
|
# if there is no record with such refresh token
|
|
34
37
|
#
|
|
35
38
|
def by_refresh_token(refresh_token)
|
|
36
|
-
|
|
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?
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
# Revokes AccessToken records that have not been revoked and associated
|
|
@@ -47,11 +56,11 @@ module Doorkeeper
|
|
|
47
56
|
def revoke_all_for(application_id, resource_owner, clock = Time)
|
|
48
57
|
where(application_id: application_id,
|
|
49
58
|
resource_owner_id: resource_owner.id,
|
|
50
|
-
revoked_at: nil)
|
|
51
|
-
update_all(revoked_at: clock.now.utc)
|
|
59
|
+
revoked_at: nil)
|
|
60
|
+
.update_all(revoked_at: clock.now.utc)
|
|
52
61
|
end
|
|
53
62
|
|
|
54
|
-
# Looking for not
|
|
63
|
+
# Looking for not revoked Access Token with a matching set of scopes
|
|
55
64
|
# that belongs to specific Application and Resource Owner.
|
|
56
65
|
#
|
|
57
66
|
# @param application [Doorkeeper::Application]
|
|
@@ -96,9 +105,9 @@ module Doorkeeper
|
|
|
96
105
|
|
|
97
106
|
(token_scopes.sort == param_scopes.sort) &&
|
|
98
107
|
Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(
|
|
99
|
-
param_scopes.to_s,
|
|
100
|
-
Doorkeeper.configuration.scopes,
|
|
101
|
-
app_scopes
|
|
108
|
+
scope_str: param_scopes.to_s,
|
|
109
|
+
server_scopes: Doorkeeper.configuration.scopes,
|
|
110
|
+
app_scopes: app_scopes
|
|
102
111
|
)
|
|
103
112
|
end
|
|
104
113
|
|
|
@@ -217,6 +226,26 @@ module Doorkeeper
|
|
|
217
226
|
accessible? && includes_scope?(*scopes)
|
|
218
227
|
end
|
|
219
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
|
+
|
|
220
249
|
private
|
|
221
250
|
|
|
222
251
|
# Generates refresh token with UniqueToken generator.
|
|
@@ -224,7 +253,8 @@ module Doorkeeper
|
|
|
224
253
|
# @return [String] refresh token value
|
|
225
254
|
#
|
|
226
255
|
def generate_refresh_token
|
|
227
|
-
|
|
256
|
+
@raw_refresh_token = UniqueToken.generate
|
|
257
|
+
self.refresh_token = hashed_or_plain_token(@raw_refresh_token)
|
|
228
258
|
end
|
|
229
259
|
|
|
230
260
|
# Generates and sets the token value with the
|
|
@@ -240,13 +270,16 @@ module Doorkeeper
|
|
|
240
270
|
def generate_token
|
|
241
271
|
self.created_at ||= Time.now.utc
|
|
242
272
|
|
|
243
|
-
|
|
273
|
+
@raw_token = token_generator.generate(
|
|
244
274
|
resource_owner_id: resource_owner_id,
|
|
245
275
|
scopes: scopes,
|
|
246
276
|
application: application,
|
|
247
277
|
expires_in: expires_in,
|
|
248
278
|
created_at: created_at
|
|
249
279
|
)
|
|
280
|
+
|
|
281
|
+
self.token = hashed_or_plain_token(@raw_token)
|
|
282
|
+
@raw_token
|
|
250
283
|
end
|
|
251
284
|
|
|
252
285
|
def token_generator
|
|
@@ -1,12 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bcrypt'
|
|
4
|
+
|
|
1
5
|
module Doorkeeper
|
|
2
6
|
module ApplicationMixin
|
|
3
7
|
extend ActiveSupport::Concern
|
|
4
8
|
|
|
5
9
|
include OAuth::Helpers
|
|
6
10
|
include Models::Orderable
|
|
11
|
+
include Models::Hashable
|
|
7
12
|
include Models::Scopes
|
|
8
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
|
|
9
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
|
+
|
|
10
38
|
# Returns an instance of the Doorkeeper::Application with
|
|
11
39
|
# specific UID and secret.
|
|
12
40
|
#
|
|
@@ -23,7 +51,7 @@ module Doorkeeper
|
|
|
23
51
|
app = by_uid(uid)
|
|
24
52
|
return unless app
|
|
25
53
|
return app if secret.blank? && !app.confidential?
|
|
26
|
-
return unless app.secret
|
|
54
|
+
return unless app.secret_matches?(secret)
|
|
27
55
|
app
|
|
28
56
|
end
|
|
29
57
|
|
|
@@ -47,5 +75,30 @@ module Doorkeeper
|
|
|
47
75
|
def redirect_uri=(uris)
|
|
48
76
|
super(uris.is_a?(Array) ? uris.join("\n") : uris)
|
|
49
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
|
|
50
103
|
end
|
|
51
104
|
end
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doorkeeper
|
|
4
|
+
module Models
|
|
5
|
+
##
|
|
6
|
+
# Hashable finder to provide lookups for input plaintext values which are
|
|
7
|
+
# mapped to their hashes before lookup.
|
|
8
|
+
module Hashable
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
delegate :perform_secret_hashing?,
|
|
12
|
+
:hashed_or_plain_token,
|
|
13
|
+
to: :class
|
|
14
|
+
|
|
15
|
+
# :nodoc
|
|
16
|
+
module ClassMethods
|
|
17
|
+
# Allow to override the hashing method by the including module
|
|
18
|
+
attr_accessor :secret_hash_function, :secret_comparer
|
|
19
|
+
|
|
20
|
+
# Compare the given plaintext with the secret
|
|
21
|
+
#
|
|
22
|
+
# @param input [String]
|
|
23
|
+
# The plain input to compare.
|
|
24
|
+
#
|
|
25
|
+
# @param secret [String]
|
|
26
|
+
# The secret value to compare with.
|
|
27
|
+
#
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
# If hashing is enabled: Whether the secret equals hashed input
|
|
30
|
+
# If hashing is disabled: Whether input matches secret
|
|
31
|
+
#
|
|
32
|
+
def secret_matches?(input, secret)
|
|
33
|
+
unless perform_secret_hashing?
|
|
34
|
+
return ActiveSupport::SecurityUtils.secure_compare input, secret
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
(secret_comparer || method(:default_comparer))
|
|
38
|
+
.call(input, secret)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns an instance of the Doorkeeper::AccessToken with
|
|
42
|
+
# specific token value.
|
|
43
|
+
#
|
|
44
|
+
# @param attr [Symbol]
|
|
45
|
+
# The token attribute we're looking with.
|
|
46
|
+
#
|
|
47
|
+
# @param token [#to_s]
|
|
48
|
+
# token value (any object that responds to `#to_s`)
|
|
49
|
+
#
|
|
50
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
|
51
|
+
# if there is no record with such token
|
|
52
|
+
#
|
|
53
|
+
def find_by_plaintext_token(attr, token)
|
|
54
|
+
token = token.to_s
|
|
55
|
+
|
|
56
|
+
find_by(attr => hashed_or_plain_token(token)) ||
|
|
57
|
+
find_by_fallback_token(attr, token)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Allow looking up previously plain tokens as a fallback
|
|
61
|
+
# IFF respective options are enabled
|
|
62
|
+
#
|
|
63
|
+
# @param attr [Symbol]
|
|
64
|
+
# The token attribute we're looking with.
|
|
65
|
+
#
|
|
66
|
+
# @param token [#to_s]
|
|
67
|
+
# token value (any object that responds to `#to_s`)
|
|
68
|
+
#
|
|
69
|
+
# @return [Doorkeeper::AccessToken, nil] AccessToken object or nil
|
|
70
|
+
# if there is no record with such token
|
|
71
|
+
#
|
|
72
|
+
def find_by_fallback_token(attr, token)
|
|
73
|
+
return nil unless perform_secret_hashing?
|
|
74
|
+
return nil unless Doorkeeper.configuration.fallback_to_plain_secrets?
|
|
75
|
+
|
|
76
|
+
find_by(attr => token).tap do |fallback|
|
|
77
|
+
upgrade_fallback_value fallback, attr
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Hash the given input token
|
|
82
|
+
#
|
|
83
|
+
# @param plain_token [String]
|
|
84
|
+
# The plain text token to hash.
|
|
85
|
+
#
|
|
86
|
+
# @return [String]
|
|
87
|
+
# IFF secret hashing enabled, the hashed token,
|
|
88
|
+
# otherwise returns the plain token.
|
|
89
|
+
def hashed_or_plain_token(plain_token)
|
|
90
|
+
if perform_secret_hashing?
|
|
91
|
+
(secret_hash_function || method(:default_hash_function))
|
|
92
|
+
.call plain_token
|
|
93
|
+
else
|
|
94
|
+
plain_token
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Allow implementations in ORMs to replace a plain
|
|
99
|
+
# value falling back to to avoid it remaining as plain text.
|
|
100
|
+
#
|
|
101
|
+
# @param instance
|
|
102
|
+
# An instance of this model with a plain value token.
|
|
103
|
+
#
|
|
104
|
+
# @param attr
|
|
105
|
+
# The token attribute to upgrade
|
|
106
|
+
#
|
|
107
|
+
def upgrade_fallback_value(instance, attr)
|
|
108
|
+
plain_token = instance.public_send attr
|
|
109
|
+
instance.update_column(attr, hashed_or_plain_token(plain_token))
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Including classes can override this function to
|
|
113
|
+
# disable or enable secret hashing dynamically
|
|
114
|
+
def perform_secret_hashing?
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Return a default hashing function to be used when including
|
|
119
|
+
# module or user does not specify what to use
|
|
120
|
+
# @param plain_token [String]
|
|
121
|
+
# The plain text token to hash.
|
|
122
|
+
#
|
|
123
|
+
# @return [String] Hashed plain text token
|
|
124
|
+
#
|
|
125
|
+
def default_hash_function(plain_token)
|
|
126
|
+
::Digest::SHA256.hexdigest plain_token
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Return a default comparer for the given hash function
|
|
130
|
+
def default_comparer(plain, secret)
|
|
131
|
+
hashed = hashed_or_plain_token(plain)
|
|
132
|
+
ActiveSupport::SecurityUtils.secure_compare hashed, secret
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
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.
|
|
19
|
+
{ action: :show, code: token.plaintext_token }
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def configuration
|