doorkeeper 5.5.4 → 5.8.1
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 +4 -4
- data/CHANGELOG.md +111 -8
- data/README.md +5 -9
- data/app/controllers/doorkeeper/authorizations_controller.rb +34 -11
- data/app/controllers/doorkeeper/tokens_controller.rb +28 -6
- data/app/views/doorkeeper/authorizations/error.html.erb +3 -1
- data/app/views/doorkeeper/authorizations/form_post.html.erb +1 -1
- data/app/views/doorkeeper/authorizations/new.html.erb +16 -16
- data/config/locales/en.yml +4 -1
- data/lib/doorkeeper/config/abstract_builder.rb +1 -1
- data/lib/doorkeeper/config/validations.rb +15 -3
- data/lib/doorkeeper/config.rb +95 -55
- data/lib/doorkeeper/engine.rb +10 -3
- data/lib/doorkeeper/errors.rb +32 -0
- data/lib/doorkeeper/helpers/controller.rb +1 -1
- data/lib/doorkeeper/models/access_token_mixin.rb +71 -9
- data/lib/doorkeeper/models/concerns/expiration_time_sql_math.rb +88 -0
- data/lib/doorkeeper/models/concerns/polymorphic_resource_owner.rb +30 -0
- data/lib/doorkeeper/oauth/authorization/code.rb +7 -1
- data/lib/doorkeeper/oauth/authorization/token.rb +7 -1
- data/lib/doorkeeper/oauth/authorization_code_request.rb +36 -12
- data/lib/doorkeeper/oauth/base_request.rb +14 -12
- data/lib/doorkeeper/oauth/client.rb +1 -1
- data/lib/doorkeeper/oauth/client_credentials/creator.rb +13 -13
- data/lib/doorkeeper/oauth/client_credentials/issuer.rb +5 -4
- data/lib/doorkeeper/oauth/client_credentials/validator.rb +4 -5
- data/lib/doorkeeper/oauth/client_credentials_request.rb +10 -2
- data/lib/doorkeeper/oauth/code_request.rb +1 -1
- data/lib/doorkeeper/oauth/error.rb +4 -3
- data/lib/doorkeeper/oauth/error_response.rb +19 -4
- data/lib/doorkeeper/oauth/helpers/uri_checker.rb +4 -4
- data/lib/doorkeeper/oauth/invalid_request_response.rb +4 -0
- data/lib/doorkeeper/oauth/password_access_token_request.rb +6 -6
- data/lib/doorkeeper/oauth/pre_authorization.rb +31 -23
- data/lib/doorkeeper/oauth/refresh_token_request.rb +17 -9
- data/lib/doorkeeper/oauth/scopes.rb +55 -1
- data/lib/doorkeeper/oauth/token_introspection.rb +34 -20
- data/lib/doorkeeper/oauth/token_request.rb +1 -1
- data/lib/doorkeeper/oauth/token_response.rb +5 -3
- data/lib/doorkeeper/orm/active_record/mixins/access_grant.rb +0 -6
- data/lib/doorkeeper/orm/active_record/mixins/access_token.rb +21 -4
- data/lib/doorkeeper/orm/active_record/mixins/application.rb +22 -4
- data/lib/doorkeeper/orm/active_record/redirect_uri_validator.rb +2 -2
- data/lib/doorkeeper/orm/active_record/stale_records_cleaner.rb +5 -2
- data/lib/doorkeeper/orm/active_record.rb +30 -37
- data/lib/doorkeeper/rails/routes.rb +12 -3
- data/lib/doorkeeper/rake/setup.rake +0 -5
- data/lib/doorkeeper/revocable_tokens/revocable_access_token.rb +21 -0
- data/lib/doorkeeper/revocable_tokens/revocable_refresh_token.rb +21 -0
- data/lib/doorkeeper/version.rb +2 -2
- data/lib/doorkeeper.rb +78 -5
- data/lib/generators/doorkeeper/remove_applications_secret_not_null_constraint_generator.rb +33 -0
- data/lib/generators/doorkeeper/templates/initializer.rb +44 -6
- data/lib/generators/doorkeeper/templates/migration.rb.erb +15 -4
- data/lib/generators/doorkeeper/templates/remove_applications_secret_not_null_constraint.rb.erb +7 -0
- metadata +28 -21
data/lib/doorkeeper/config.rb
CHANGED
@@ -5,59 +5,11 @@ require "doorkeeper/config/option"
|
|
5
5
|
require "doorkeeper/config/validations"
|
6
6
|
|
7
7
|
module Doorkeeper
|
8
|
-
# Defines a MissingConfiguration error for a missing Doorkeeper configuration
|
9
|
-
#
|
10
|
-
class MissingConfiguration < StandardError
|
11
|
-
def initialize
|
12
|
-
super("Configuration for doorkeeper missing. Do you have doorkeeper initializer?")
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
8
|
# Doorkeeper option DSL could be reused in extensions to build their own
|
17
9
|
# configurations. To use the Option DSL gems need to define `builder_class` method
|
18
10
|
# that returns configuration Builder class. This exception raises when they don't
|
19
11
|
# define it.
|
20
12
|
#
|
21
|
-
class MissingConfigurationBuilderClass < StandardError; end
|
22
|
-
|
23
|
-
class << self
|
24
|
-
def configure(&block)
|
25
|
-
@config = Config::Builder.new(&block).build
|
26
|
-
setup_orm_adapter
|
27
|
-
setup_orm_models
|
28
|
-
setup_application_owner if @config.enable_application_owner?
|
29
|
-
@config
|
30
|
-
end
|
31
|
-
|
32
|
-
# @return [Doorkeeper::Config] configuration instance
|
33
|
-
#
|
34
|
-
def configuration
|
35
|
-
@config || (raise MissingConfiguration)
|
36
|
-
end
|
37
|
-
|
38
|
-
alias config configuration
|
39
|
-
|
40
|
-
def setup_orm_adapter
|
41
|
-
@orm_adapter = "doorkeeper/orm/#{configuration.orm}".classify.constantize
|
42
|
-
rescue NameError => e
|
43
|
-
raise e, "ORM adapter not found (#{configuration.orm})", <<-ERROR_MSG.strip_heredoc
|
44
|
-
[DOORKEEPER] ORM adapter not found (#{configuration.orm}), or there was an error
|
45
|
-
trying to load it.
|
46
|
-
|
47
|
-
You probably need to add the related gem for this adapter to work with
|
48
|
-
doorkeeper.
|
49
|
-
ERROR_MSG
|
50
|
-
end
|
51
|
-
|
52
|
-
def setup_orm_models
|
53
|
-
@orm_adapter.initialize_models!
|
54
|
-
end
|
55
|
-
|
56
|
-
def setup_application_owner
|
57
|
-
@orm_adapter.initialize_application_owner!
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
13
|
class Config
|
62
14
|
# Default Doorkeeper configuration builder
|
63
15
|
class Builder < AbstractBuilder
|
@@ -79,6 +31,16 @@ module Doorkeeper
|
|
79
31
|
@config.instance_variable_set(:@confirm_application_owner, true)
|
80
32
|
end
|
81
33
|
|
34
|
+
# Provide support for dynamic scopes (e.g. user:*) (disabled by default)
|
35
|
+
# Optional parameter delimiter (default ":") if you want to customize
|
36
|
+
# the delimiter separating the scope name and matching value.
|
37
|
+
#
|
38
|
+
# @param opts [Hash] the options to configure dynamic scopes
|
39
|
+
def enable_dynamic_scopes(opts = {})
|
40
|
+
@config.instance_variable_set(:@enable_dynamic_scopes, true)
|
41
|
+
@config.instance_variable_set(:@dynamic_scopes_delimiter, opts[:delimiter] || ':')
|
42
|
+
end
|
43
|
+
|
82
44
|
# Define default access token scopes for your provider
|
83
45
|
#
|
84
46
|
# @param scopes [Array] Default set of access (OAuth::Scopes.new)
|
@@ -137,6 +99,15 @@ module Doorkeeper
|
|
137
99
|
@config.instance_variable_set(:@reuse_access_token, true)
|
138
100
|
end
|
139
101
|
|
102
|
+
# Choose to use the url path for native autorization codes
|
103
|
+
# Enabling this flag sets the authorization code response route for
|
104
|
+
# native redirect uris to oauth/authorize/<code>. The default is
|
105
|
+
# oauth/authorize/native?code=<code>.
|
106
|
+
# Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/1143
|
107
|
+
def use_url_path_for_native_authorization
|
108
|
+
@config.instance_variable_set(:@use_url_path_for_native_authorization, true)
|
109
|
+
end
|
110
|
+
|
140
111
|
# TODO: maybe make it more generic for other flows too?
|
141
112
|
# Only allow one valid access token obtained via client credentials
|
142
113
|
# per client. If a new access token is obtained before the old one
|
@@ -145,6 +116,19 @@ module Doorkeeper
|
|
145
116
|
@config.instance_variable_set(:@revoke_previous_client_credentials_token, true)
|
146
117
|
end
|
147
118
|
|
119
|
+
# Only allow one valid access token obtained via authorization code
|
120
|
+
# per client. If a new access token is obtained before the old one
|
121
|
+
# expired, the old one gets revoked (disabled by default)
|
122
|
+
def revoke_previous_authorization_code_token
|
123
|
+
@config.instance_variable_set(:@revoke_previous_authorization_code_token, true)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Require non-confidential apps to use PKCE (send a code_verifier) when requesting
|
127
|
+
# an access_token using an authorization code (disabled by default)
|
128
|
+
def force_pkce
|
129
|
+
@config.instance_variable_set(:@force_pkce, true)
|
130
|
+
end
|
131
|
+
|
148
132
|
# Use an API mode for applications generated with --api argument
|
149
133
|
# It will skip applications controller, disable forgery protection
|
150
134
|
def api_only
|
@@ -269,6 +253,7 @@ module Doorkeeper
|
|
269
253
|
option :orm, default: :active_record
|
270
254
|
option :native_redirect_uri, default: "urn:ietf:wg:oauth:2.0:oob", deprecated: true
|
271
255
|
option :grant_flows, default: %w[authorization_code client_credentials]
|
256
|
+
option :pkce_code_challenge_methods, default: %w[plain S256]
|
272
257
|
option :handle_auth_errors, default: :render
|
273
258
|
option :token_lookup_batch_size, default: 10_000
|
274
259
|
# Sets the token_reuse_limit
|
@@ -293,10 +278,6 @@ module Doorkeeper
|
|
293
278
|
option :skip_client_authentication_for_password_grant,
|
294
279
|
default: false
|
295
280
|
|
296
|
-
option :active_record_options,
|
297
|
-
default: {},
|
298
|
-
deprecated: { message: "Customize Doorkeeper models instead" }
|
299
|
-
|
300
281
|
# Hook to allow arbitrary user-client authorization
|
301
282
|
option :authorize_resource_owner_for_client,
|
302
283
|
default: ->(_client, _resource_owner) { true }
|
@@ -364,11 +345,29 @@ module Doorkeeper
|
|
364
345
|
option :access_token_generator,
|
365
346
|
default: "Doorkeeper::OAuth::Helpers::UniqueToken"
|
366
347
|
|
348
|
+
# Allows additional data to be received when granting access to an Application, and for this
|
349
|
+
# additional data to be sent with subsequently generated access tokens. The access grant and
|
350
|
+
# access token models will both need to respond to the specified attribute names.
|
351
|
+
#
|
352
|
+
# @param attributes [Array] The array of custom attribute names to be saved
|
353
|
+
#
|
354
|
+
option :custom_access_token_attributes,
|
355
|
+
default: []
|
356
|
+
|
357
|
+
# Use a custom class for generating the application secret.
|
358
|
+
# https://doorkeeper.gitbook.io/guides/configuration/other-configurations#custom-application-secret-generator
|
359
|
+
#
|
360
|
+
# @param application_secret_generator [String]
|
361
|
+
# the name of the application secret generator class
|
362
|
+
#
|
363
|
+
option :application_secret_generator,
|
364
|
+
default: "Doorkeeper::OAuth::Helpers::UniqueToken"
|
365
|
+
|
367
366
|
# Default access token generator is a SecureRandom class from Ruby stdlib.
|
368
367
|
# This option defines which method will be used to generate a unique token value.
|
369
368
|
#
|
370
|
-
# @param
|
371
|
-
# the name of the access token generator
|
369
|
+
# @param default_generator_method [Symbol]
|
370
|
+
# the method name of the default access token generator
|
372
371
|
#
|
373
372
|
option :default_generator_method, default: :urlsafe_base64
|
374
373
|
|
@@ -430,7 +429,7 @@ module Doorkeeper
|
|
430
429
|
default: (lambda do |token, authorized_client, authorized_token|
|
431
430
|
if authorized_token
|
432
431
|
authorized_token.application == token&.application
|
433
|
-
elsif token
|
432
|
+
elsif token&.application
|
434
433
|
authorized_client == token.application
|
435
434
|
else
|
436
435
|
true
|
@@ -441,6 +440,16 @@ module Doorkeeper
|
|
441
440
|
:token_secret_fallback_strategy,
|
442
441
|
:application_secret_fallback_strategy
|
443
442
|
|
443
|
+
def clear_cache!
|
444
|
+
%i[
|
445
|
+
application_model
|
446
|
+
access_token_model
|
447
|
+
access_grant_model
|
448
|
+
].each do |var|
|
449
|
+
remove_instance_variable("@#{var}") if instance_variable_defined?("@#{var}")
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
444
453
|
# Doorkeeper Access Token model class.
|
445
454
|
#
|
446
455
|
# @return [ActiveRecord::Base, Mongoid::Document, Sequel::Model]
|
@@ -496,6 +505,14 @@ module Doorkeeper
|
|
496
505
|
option_set? :revoke_previous_client_credentials_token
|
497
506
|
end
|
498
507
|
|
508
|
+
def revoke_previous_authorization_code_token?
|
509
|
+
option_set? :revoke_previous_authorization_code_token
|
510
|
+
end
|
511
|
+
|
512
|
+
def force_pkce?
|
513
|
+
option_set? :force_pkce
|
514
|
+
end
|
515
|
+
|
499
516
|
def enforce_configured_scopes?
|
500
517
|
option_set? :enforce_configured_scopes
|
501
518
|
end
|
@@ -504,6 +521,14 @@ module Doorkeeper
|
|
504
521
|
option_set? :enable_application_owner
|
505
522
|
end
|
506
523
|
|
524
|
+
def enable_dynamic_scopes?
|
525
|
+
option_set? :enable_dynamic_scopes
|
526
|
+
end
|
527
|
+
|
528
|
+
def dynamic_scopes_delimiter
|
529
|
+
@dynamic_scopes_delimiter
|
530
|
+
end
|
531
|
+
|
507
532
|
def polymorphic_resource_owner?
|
508
533
|
option_set? :polymorphic_resource_owner
|
509
534
|
end
|
@@ -516,6 +541,10 @@ module Doorkeeper
|
|
516
541
|
handle_auth_errors == :raise
|
517
542
|
end
|
518
543
|
|
544
|
+
def redirect_on_errors?
|
545
|
+
handle_auth_errors == :redirect
|
546
|
+
end
|
547
|
+
|
519
548
|
def application_secret_hashed?
|
520
549
|
instance_variable_defined?(:"@application_secret_strategy")
|
521
550
|
end
|
@@ -544,6 +573,12 @@ module Doorkeeper
|
|
544
573
|
@scopes_by_grant_type ||= {}
|
545
574
|
end
|
546
575
|
|
576
|
+
def pkce_code_challenge_methods_supported
|
577
|
+
return [] unless access_grant_model.pkce_supported?
|
578
|
+
|
579
|
+
pkce_code_challenge_methods
|
580
|
+
end
|
581
|
+
|
547
582
|
def client_credentials_methods
|
548
583
|
@client_credentials_methods ||= %i[from_basic from_params]
|
549
584
|
end
|
@@ -581,6 +616,11 @@ module Doorkeeper
|
|
581
616
|
def deprecated_token_grant_types_resolver
|
582
617
|
@deprecated_token_grant_types ||= calculate_token_grant_types
|
583
618
|
end
|
619
|
+
|
620
|
+
def native_authorization_code_route
|
621
|
+
@use_url_path_for_native_authorization = false unless defined?(@use_url_path_for_native_authorization)
|
622
|
+
@use_url_path_for_native_authorization ? '/:code' : '/native'
|
623
|
+
end
|
584
624
|
|
585
625
|
# [NOTE]: deprecated and will be removed soon
|
586
626
|
def deprecated_authorization_flows
|
data/lib/doorkeeper/engine.rb
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
module Doorkeeper
|
4
4
|
class Engine < Rails::Engine
|
5
|
-
initializer "doorkeeper.params.filter" do |app|
|
6
|
-
|
7
|
-
|
5
|
+
initializer "doorkeeper.params.filter", after: :load_config_initializers do |app|
|
6
|
+
if Doorkeeper.configured?
|
7
|
+
parameters = %w[client_secret authentication_token access_token refresh_token]
|
8
|
+
parameters << "code" if Doorkeeper.config.grant_flows.include?("authorization_code")
|
9
|
+
app.config.filter_parameters << /^(#{Regexp.union(parameters)})$/
|
10
|
+
end
|
8
11
|
end
|
9
12
|
|
10
13
|
initializer "doorkeeper.routes" do
|
@@ -17,6 +20,10 @@ module Doorkeeper
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
config.to_prepare do
|
24
|
+
Doorkeeper.run_orm_hooks
|
25
|
+
end
|
26
|
+
|
20
27
|
if defined?(Sprockets) && Sprockets::VERSION.chr.to_i >= 4
|
21
28
|
initializer "doorkeeper.assets.precompile" do |app|
|
22
29
|
# Force users to use:
|
data/lib/doorkeeper/errors.rb
CHANGED
@@ -6,6 +6,10 @@ module Doorkeeper
|
|
6
6
|
def type
|
7
7
|
message
|
8
8
|
end
|
9
|
+
|
10
|
+
def self.translate_options
|
11
|
+
{}
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
15
|
class InvalidGrantReuse < DoorkeeperError
|
@@ -39,13 +43,41 @@ module Doorkeeper
|
|
39
43
|
def initialize(response)
|
40
44
|
@response = response
|
41
45
|
end
|
46
|
+
|
47
|
+
def self.name_for_response
|
48
|
+
self.name.demodulize.underscore.to_sym
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class InvalidCodeChallengeMethod < BaseResponseError
|
53
|
+
def self.translate_options
|
54
|
+
challenge_methods = Doorkeeper.config.pkce_code_challenge_methods_supported
|
55
|
+
{
|
56
|
+
challenge_methods: challenge_methods.join(", "),
|
57
|
+
count: challenge_methods.length
|
58
|
+
}
|
59
|
+
end
|
42
60
|
end
|
43
61
|
|
44
62
|
UnableToGenerateToken = Class.new(DoorkeeperError)
|
45
63
|
TokenGeneratorNotFound = Class.new(DoorkeeperError)
|
46
64
|
NoOrmCleaner = Class.new(DoorkeeperError)
|
47
65
|
|
66
|
+
InvalidRequest = Class.new(BaseResponseError)
|
48
67
|
InvalidToken = Class.new(BaseResponseError)
|
68
|
+
InvalidClient = Class.new(BaseResponseError)
|
69
|
+
InvalidScope = Class.new(BaseResponseError)
|
70
|
+
InvalidRedirectUri = Class.new(BaseResponseError)
|
71
|
+
InvalidCodeChallenge = Class.new(BaseResponseError)
|
72
|
+
InvalidGrant = Class.new(BaseResponseError)
|
73
|
+
|
74
|
+
UnauthorizedClient = Class.new(BaseResponseError)
|
75
|
+
UnsupportedResponseType = Class.new(BaseResponseError)
|
76
|
+
UnsupportedResponseMode = Class.new(BaseResponseError)
|
77
|
+
|
78
|
+
AccessDenied = Class.new(BaseResponseError)
|
79
|
+
ServerError = Class.new(BaseResponseError)
|
80
|
+
|
49
81
|
TokenExpired = Class.new(InvalidToken)
|
50
82
|
TokenRevoked = Class.new(InvalidToken)
|
51
83
|
TokenUnknown = Class.new(InvalidToken)
|
@@ -13,6 +13,7 @@ module Doorkeeper
|
|
13
13
|
include Models::SecretStorable
|
14
14
|
include Models::Scopes
|
15
15
|
include Models::ResourceOwnerable
|
16
|
+
include Models::ExpirationTimeSqlMath
|
16
17
|
|
17
18
|
module ClassMethods
|
18
19
|
# Returns an instance of the Doorkeeper::AccessToken with
|
@@ -82,13 +83,18 @@ module Doorkeeper
|
|
82
83
|
# Resource Owner model instance or it's ID
|
83
84
|
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
84
85
|
# set of scopes
|
86
|
+
# @param custom_attributes [Nilable Hash]
|
87
|
+
# A nil value, or hash with keys corresponding to the custom attributes
|
88
|
+
# configured with the `custom_access_token_attributes` config option.
|
89
|
+
# A nil value will ignore custom attributes.
|
85
90
|
#
|
86
91
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
87
92
|
# nil if matching record was not found
|
88
93
|
#
|
89
|
-
def matching_token_for(application, resource_owner, scopes)
|
94
|
+
def matching_token_for(application, resource_owner, scopes, custom_attributes: nil, include_expired: true)
|
90
95
|
tokens = authorized_tokens_for(application&.id, resource_owner)
|
91
|
-
|
96
|
+
tokens = tokens.not_expired unless include_expired
|
97
|
+
find_matching_token(tokens, application, custom_attributes, scopes)
|
92
98
|
end
|
93
99
|
|
94
100
|
# Interface to enumerate access token records in batches in order not
|
@@ -104,9 +110,7 @@ module Doorkeeper
|
|
104
110
|
#
|
105
111
|
# ActiveRecord 5.x - 6.x ignores custom ordering so we can't perform a
|
106
112
|
# database sort by created_at, so we need to load all the matching records,
|
107
|
-
# sort them and find latest one.
|
108
|
-
# query using Time math if possible, but we n eed to consider ORM and
|
109
|
-
# different databases support.
|
113
|
+
# sort them and find latest one.
|
110
114
|
#
|
111
115
|
# @param relation [ActiveRecord::Relation]
|
112
116
|
# Access tokens relation
|
@@ -114,11 +118,15 @@ module Doorkeeper
|
|
114
118
|
# Application instance
|
115
119
|
# @param scopes [String, Doorkeeper::OAuth::Scopes]
|
116
120
|
# set of scopes
|
121
|
+
# @param custom_attributes [Nilable Hash]
|
122
|
+
# A nil value, or hash with keys corresponding to the custom attributes
|
123
|
+
# configured with the `custom_access_token_attributes` config option.
|
124
|
+
# A nil value will ignore custom attributes.
|
117
125
|
#
|
118
126
|
# @return [Doorkeeper::AccessToken, nil] Access Token instance or
|
119
127
|
# nil if matching record was not found
|
120
128
|
#
|
121
|
-
def find_matching_token(relation, application, scopes)
|
129
|
+
def find_matching_token(relation, application, custom_attributes, scopes)
|
122
130
|
return nil unless relation
|
123
131
|
|
124
132
|
matching_tokens = []
|
@@ -126,7 +134,8 @@ module Doorkeeper
|
|
126
134
|
|
127
135
|
find_access_token_in_batches(relation, batch_size: batch_size) do |batch|
|
128
136
|
tokens = batch.select do |token|
|
129
|
-
scopes_match?(token.scopes, scopes, application&.scopes)
|
137
|
+
scopes_match?(token.scopes, scopes, application&.scopes) &&
|
138
|
+
custom_attributes_match?(token, custom_attributes)
|
130
139
|
end
|
131
140
|
|
132
141
|
matching_tokens.concat(tokens)
|
@@ -160,6 +169,31 @@ module Doorkeeper
|
|
160
169
|
)
|
161
170
|
end
|
162
171
|
|
172
|
+
# Checks whether the token custom attribute values match the custom
|
173
|
+
# attributes from the parameters.
|
174
|
+
#
|
175
|
+
# @param token [Doorkeeper::AccessToken]
|
176
|
+
# The access token whose custom attributes are being compared
|
177
|
+
# to the custom_attributes.
|
178
|
+
#
|
179
|
+
# @param custom_attributes [Hash]
|
180
|
+
# A hash of the attributes for which we want to determine whether
|
181
|
+
# the token's custom attributes match.
|
182
|
+
#
|
183
|
+
# @return [Boolean] true if the token's custom attribute values
|
184
|
+
# match those in the custom_attributes, or if both are empty/blank.
|
185
|
+
# False otherwise.
|
186
|
+
def custom_attributes_match?(token, custom_attributes)
|
187
|
+
return true if custom_attributes.nil?
|
188
|
+
|
189
|
+
token_attribs = token.custom_attributes
|
190
|
+
return true if token_attribs.blank? && custom_attributes.blank?
|
191
|
+
|
192
|
+
Doorkeeper.config.custom_access_token_attributes.all? do |attribute|
|
193
|
+
token_attribs[attribute] == custom_attributes[attribute]
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
163
197
|
# Looking for not expired AccessToken record with a matching set of
|
164
198
|
# scopes that belongs to specific Application and Resource Owner.
|
165
199
|
# If it doesn't exists - then creates it.
|
@@ -180,8 +214,12 @@ module Doorkeeper
|
|
180
214
|
# @return [Doorkeeper::AccessToken] existing record or a new one
|
181
215
|
#
|
182
216
|
def find_or_create_for(application:, resource_owner:, scopes:, **token_attributes)
|
217
|
+
scopes = Doorkeeper::OAuth::Scopes.from_string(scopes) if scopes.is_a?(String)
|
218
|
+
|
183
219
|
if Doorkeeper.config.reuse_access_token
|
184
|
-
|
220
|
+
custom_attributes = extract_custom_attributes(token_attributes).presence
|
221
|
+
access_token = matching_token_for(
|
222
|
+
application, resource_owner, scopes, custom_attributes: custom_attributes, include_expired: false)
|
185
223
|
|
186
224
|
return access_token if access_token&.reusable?
|
187
225
|
end
|
@@ -213,7 +251,7 @@ module Doorkeeper
|
|
213
251
|
# @return [Doorkeeper::AccessToken] new access token
|
214
252
|
#
|
215
253
|
def create_for(application:, resource_owner:, scopes:, **token_attributes)
|
216
|
-
token_attributes[:
|
254
|
+
token_attributes[:application] = application
|
217
255
|
token_attributes[:scopes] = scopes.to_s
|
218
256
|
|
219
257
|
if Doorkeeper.config.polymorphic_resource_owner?
|
@@ -276,6 +314,18 @@ module Doorkeeper
|
|
276
314
|
def fallback_secret_strategy
|
277
315
|
::Doorkeeper.config.token_secret_fallback_strategy
|
278
316
|
end
|
317
|
+
|
318
|
+
# Extracts the token's custom attributes (defined by the
|
319
|
+
# custom_access_token_attributes config option) from the token's attributes.
|
320
|
+
#
|
321
|
+
# @param attributes [Hash]
|
322
|
+
# A hash of the access token's attributes.
|
323
|
+
# @return [Hash]
|
324
|
+
# A hash containing only the custom access token attributes.
|
325
|
+
def extract_custom_attributes(attributes)
|
326
|
+
attributes.with_indifferent_access.slice(
|
327
|
+
*Doorkeeper.configuration.custom_access_token_attributes)
|
328
|
+
end
|
279
329
|
end
|
280
330
|
|
281
331
|
# Access Token type: Bearer.
|
@@ -308,6 +358,14 @@ module Doorkeeper
|
|
308
358
|
end
|
309
359
|
end
|
310
360
|
|
361
|
+
# The token's custom attributes, as defined by
|
362
|
+
# the custom_access_token_attributes config option.
|
363
|
+
#
|
364
|
+
# @return [Hash] hash of custom access token attributes.
|
365
|
+
def custom_attributes
|
366
|
+
self.class.extract_custom_attributes(attributes)
|
367
|
+
end
|
368
|
+
|
311
369
|
# Indicates whether the token instance have the same credential
|
312
370
|
# as the other Access Token.
|
313
371
|
#
|
@@ -435,6 +493,10 @@ module Doorkeeper
|
|
435
493
|
if Doorkeeper.config.polymorphic_resource_owner?
|
436
494
|
attributes[:resource_owner] = resource_owner
|
437
495
|
end
|
496
|
+
|
497
|
+
Doorkeeper.config.custom_access_token_attributes.each do |attribute_name|
|
498
|
+
attributes[attribute_name] = public_send(attribute_name)
|
499
|
+
end
|
438
500
|
end
|
439
501
|
end
|
440
502
|
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module Models
|
5
|
+
module ExpirationTimeSqlMath
|
6
|
+
extend ::ActiveSupport::Concern
|
7
|
+
|
8
|
+
class ExpirationTimeSqlGenerator
|
9
|
+
attr_reader :model
|
10
|
+
|
11
|
+
delegate :table_name, to: :@model
|
12
|
+
|
13
|
+
def initialize(model)
|
14
|
+
@model = model
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate_sql
|
18
|
+
raise "`generate_sql` should be overridden for a #{self.class.name}!"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MySqlExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
23
|
+
def generate_sql
|
24
|
+
Arel.sql("DATE_ADD(#{table_name}.created_at, INTERVAL #{table_name}.expires_in SECOND)")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class SqlLiteExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
29
|
+
def generate_sql
|
30
|
+
Arel.sql("DATETIME(#{table_name}.created_at, '+' || #{table_name}.expires_in || ' SECONDS')")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class SqlServerExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
35
|
+
def generate_sql
|
36
|
+
Arel.sql("DATEADD(second, #{table_name}.expires_in, #{table_name}.created_at) AT TIME ZONE 'UTC'")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class OracleExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
41
|
+
def generate_sql
|
42
|
+
Arel.sql("#{table_name}.created_at + INTERVAL to_char(#{table_name}.expires_in) second")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class PostgresExpirationTimeSqlGenerator < ExpirationTimeSqlGenerator
|
47
|
+
def generate_sql
|
48
|
+
Arel.sql("#{table_name}.created_at + #{table_name}.expires_in * INTERVAL '1 SECOND'")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
ADAPTERS_MAPPING = {
|
53
|
+
"sqlite" => SqlLiteExpirationTimeSqlGenerator,
|
54
|
+
"sqlite3" => SqlLiteExpirationTimeSqlGenerator,
|
55
|
+
"postgis" => PostgresExpirationTimeSqlGenerator,
|
56
|
+
"postgresql" => PostgresExpirationTimeSqlGenerator,
|
57
|
+
"mysql" => MySqlExpirationTimeSqlGenerator,
|
58
|
+
"mysql2" => MySqlExpirationTimeSqlGenerator,
|
59
|
+
"trilogy" => MySqlExpirationTimeSqlGenerator,
|
60
|
+
"sqlserver" => SqlServerExpirationTimeSqlGenerator,
|
61
|
+
"oracleenhanced" => OracleExpirationTimeSqlGenerator,
|
62
|
+
}.freeze
|
63
|
+
|
64
|
+
module ClassMethods
|
65
|
+
def supports_expiration_time_math?
|
66
|
+
ADAPTERS_MAPPING.key?(adapter_name.downcase) ||
|
67
|
+
respond_to?(:custom_expiration_time_sql)
|
68
|
+
end
|
69
|
+
|
70
|
+
def expiration_time_sql
|
71
|
+
if respond_to?(:custom_expiration_time_sql)
|
72
|
+
custom_expiration_time_sql
|
73
|
+
else
|
74
|
+
expiration_time_sql_expression
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def expiration_time_sql_expression
|
79
|
+
ADAPTERS_MAPPING.fetch(adapter_name.downcase).new(self).generate_sql
|
80
|
+
end
|
81
|
+
|
82
|
+
def adapter_name
|
83
|
+
ActiveRecord::Base.connection.adapter_name
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Doorkeeper
|
4
|
+
module Models
|
5
|
+
module PolymorphicResourceOwner
|
6
|
+
module ForAccessGrant
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
11
|
+
belongs_to :resource_owner, polymorphic: true, optional: false
|
12
|
+
else
|
13
|
+
validates :resource_owner_id, presence: true
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ForAccessToken
|
19
|
+
extend ActiveSupport::Concern
|
20
|
+
|
21
|
+
included do
|
22
|
+
if Doorkeeper.config.polymorphic_resource_owner?
|
23
|
+
belongs_to :resource_owner, polymorphic: true, optional: true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -45,7 +45,13 @@ module Doorkeeper
|
|
45
45
|
attributes[:resource_owner_id] = resource_owner.id
|
46
46
|
end
|
47
47
|
|
48
|
-
pkce_attributes.merge(attributes)
|
48
|
+
pkce_attributes.merge(attributes).merge(custom_attributes)
|
49
|
+
end
|
50
|
+
|
51
|
+
def custom_attributes
|
52
|
+
# Custom access token attributes are saved into the access grant,
|
53
|
+
# and then included in subsequently generated access tokens.
|
54
|
+
@pre_auth.custom_access_token_attributes.to_h.with_indifferent_access
|
49
55
|
end
|
50
56
|
|
51
57
|
def pkce_attributes
|
@@ -60,7 +60,7 @@ module Doorkeeper
|
|
60
60
|
)
|
61
61
|
|
62
62
|
@token = Doorkeeper.config.access_token_model.find_or_create_for(
|
63
|
-
application:
|
63
|
+
application: application,
|
64
64
|
resource_owner: resource_owner,
|
65
65
|
scopes: pre_auth.scopes,
|
66
66
|
expires_in: self.class.access_token_expires_in(Doorkeeper.config, context),
|
@@ -68,6 +68,12 @@ module Doorkeeper
|
|
68
68
|
)
|
69
69
|
end
|
70
70
|
|
71
|
+
def application
|
72
|
+
return unless pre_auth.client
|
73
|
+
|
74
|
+
pre_auth.client.is_a?(Doorkeeper.config.application_model) ? pre_auth.client : pre_auth.client.application
|
75
|
+
end
|
76
|
+
|
71
77
|
def oob_redirect
|
72
78
|
{
|
73
79
|
controller: controller,
|