doorkeeper-sequel 1.5.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.editorconfig +6 -0
- data/.gitignore +1 -0
- data/.gitmodules +0 -1
- data/.rubocop.yml +29 -2
- data/.travis.yml +3 -12
- data/CHANGELOG.md +13 -3
- data/Gemfile +13 -9
- data/README.md +18 -3
- data/Rakefile +22 -18
- data/config/locales/en.yml +11 -0
- data/doorkeeper-sequel.gemspec +27 -23
- data/gemfiles/rails-5.0.gemfile +9 -6
- data/gemfiles/rails-5.1.gemfile +9 -6
- data/gemfiles/rails-5.2.gemfile +9 -6
- data/gemfiles/rails-6.0.gemfile +14 -0
- data/lib/doorkeeper-sequel.rb +21 -17
- data/lib/doorkeeper-sequel/gem_version.rb +5 -3
- data/lib/doorkeeper-sequel/generators/application_owner_generator.rb +5 -3
- data/lib/doorkeeper-sequel/generators/concerns/migration_actions.rb +8 -6
- data/lib/doorkeeper-sequel/generators/confidential_applications_generator.rb +5 -3
- data/lib/doorkeeper-sequel/generators/migration_generator.rb +5 -3
- data/lib/doorkeeper-sequel/generators/pkce_generator.rb +16 -0
- data/lib/doorkeeper-sequel/generators/polymorphic_resource_owner_generator.rb +16 -0
- data/lib/doorkeeper-sequel/generators/previous_refresh_token_generator.rb +5 -3
- data/lib/doorkeeper-sequel/generators/templates/add_confidential_to_application_migration.rb +2 -0
- data/lib/doorkeeper-sequel/generators/templates/add_owner_to_application.rb +3 -1
- data/lib/doorkeeper-sequel/generators/templates/add_previous_refresh_token_to_access_tokens.rb +3 -1
- data/lib/doorkeeper-sequel/generators/templates/create_doorkeeper_tables.rb +7 -3
- data/lib/doorkeeper-sequel/generators/templates/enable_pkce_migration.rb +10 -0
- data/lib/doorkeeper-sequel/generators/templates/enable_polymorphic_resource_owner_migration.rb +15 -0
- data/lib/doorkeeper-sequel/mixins/access_grant_mixin.rb +45 -7
- data/lib/doorkeeper-sequel/mixins/access_token_mixin.rb +154 -42
- data/lib/doorkeeper-sequel/mixins/application_mixin.rb +116 -22
- data/lib/doorkeeper-sequel/mixins/concerns/ownership.rb +2 -0
- data/lib/doorkeeper-sequel/mixins/concerns/sequel_compat.rb +33 -3
- data/lib/doorkeeper-sequel/railtie.rb +3 -1
- data/lib/doorkeeper-sequel/tasks/doorkeeper-sequel.rake +20 -3
- data/lib/doorkeeper-sequel/validators/redirect_uri_validator.rb +35 -6
- data/lib/doorkeeper-sequel/version.rb +3 -1
- data/lib/doorkeeper/orm/sequel.rb +11 -4
- data/lib/doorkeeper/orm/sequel/access_grant.rb +18 -0
- data/lib/doorkeeper/orm/sequel/access_token.rb +6 -0
- data/lib/doorkeeper/orm/sequel/application.rb +34 -3
- data/lib/doorkeeper/orm/sequel/stale_records_cleaner.rb +26 -0
- data/lib/doorkeeper/redirect_uri_validator.rb +9 -0
- data/spec/stubs/config/initializers/db.rb +6 -1
- data/spec/stubs/spec_helper_integration.rb +9 -11
- metadata +225 -235
- data/gemfiles/rails-4.2.gemfile +0 -13
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../validators/redirect_uri_validator"
|
2
4
|
|
3
5
|
module DoorkeeperSequel
|
4
6
|
module ApplicationMixin
|
@@ -7,6 +9,7 @@ module DoorkeeperSequel
|
|
7
9
|
include SequelCompat
|
8
10
|
include Doorkeeper::OAuth::Helpers
|
9
11
|
include Doorkeeper::Models::Scopes
|
12
|
+
include Doorkeeper::Models::SecretStorable
|
10
13
|
include DoorkeeperSequel::RedirectUriValidator
|
11
14
|
|
12
15
|
included do
|
@@ -14,30 +17,86 @@ module DoorkeeperSequel
|
|
14
17
|
plugin :timestamps
|
15
18
|
plugin :association_dependencies
|
16
19
|
|
17
|
-
one_to_many :access_grants, class:
|
18
|
-
one_to_many :access_tokens, class:
|
20
|
+
one_to_many :access_grants, class: "Doorkeeper::AccessGrant"
|
21
|
+
one_to_many :access_tokens, class: "Doorkeeper::AccessToken"
|
19
22
|
|
20
23
|
add_association_dependencies access_grants: :delete, access_tokens: :delete
|
21
24
|
|
22
25
|
set_allowed_columns :name, :redirect_uri, :scopes, :confidential
|
23
26
|
|
24
27
|
def before_validation
|
25
|
-
|
26
|
-
|
28
|
+
if new?
|
29
|
+
generate_uid
|
30
|
+
generate_secret
|
31
|
+
end
|
27
32
|
super
|
28
33
|
end
|
29
34
|
|
30
35
|
def validate
|
31
36
|
super
|
32
|
-
validates_presence [
|
37
|
+
validates_presence %i[name secret uid]
|
33
38
|
validates_unique [:uid]
|
34
39
|
validates_redirect_uri :redirect_uri
|
35
40
|
validates_includes [true, false], :confidential, allow_missing: true
|
36
41
|
|
42
|
+
validate_scopes_match_configured if enforce_scopes?
|
43
|
+
|
37
44
|
if respond_to?(:validate_owner?)
|
38
45
|
validates_presence [:owner_id] if validate_owner?
|
39
46
|
end
|
40
47
|
end
|
48
|
+
|
49
|
+
# In some cases, Doorkeeper used as a proxy app. In this case database does not have any fields.
|
50
|
+
# Even table may not exists on source database.
|
51
|
+
# Aliasing this method throws NoMethod Error. Due to this we need to explicitly
|
52
|
+
# define confidential? here.
|
53
|
+
def confidential?
|
54
|
+
confidential.present? && !!confidential
|
55
|
+
end
|
56
|
+
|
57
|
+
def renew_secret
|
58
|
+
@raw_secret = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
59
|
+
secret_strategy.store_secret(self, :secret, @raw_secret)
|
60
|
+
end
|
61
|
+
|
62
|
+
def as_json(options = {})
|
63
|
+
if (respond_to?(:owner) && owner && owner == options[:current_resource_owner]) ||
|
64
|
+
options[:as_owner]
|
65
|
+
hash = JSON.parse(to_json, symbolize_names: false)
|
66
|
+
else
|
67
|
+
only = extract_serializable_attributes(options)
|
68
|
+
# TODO: Write our own serializer for Hash
|
69
|
+
hash = JSON.parse(to_json(options.merge(only: only)), symbolize_names: false)
|
70
|
+
end
|
71
|
+
hash["secret"] = plaintext_secret if hash.key?("secret")
|
72
|
+
hash
|
73
|
+
end
|
74
|
+
|
75
|
+
def plaintext_secret
|
76
|
+
if secret_strategy.allows_restoring_secrets?
|
77
|
+
secret_strategy.restore_secret(self, :secret)
|
78
|
+
else
|
79
|
+
@raw_secret
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def authorized_for_resource_owner?(resource_owner)
|
84
|
+
Doorkeeper.configuration.authorize_resource_owner_for_client.call(self, resource_owner)
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def validate_scopes_match_configured
|
90
|
+
if scopes.present? && !Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(scope_str: scopes.to_s,
|
91
|
+
server_scopes: Doorkeeper.configuration.scopes)
|
92
|
+
scope = "sequel.errors.models.doorkeeper/application.attributes.scopes"
|
93
|
+
errors.add(:scopes, I18n.t(:not_match_configured, scope: scope))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def enforce_scopes?
|
98
|
+
Doorkeeper.configuration.enforce_configured_scopes?
|
99
|
+
end
|
41
100
|
end
|
42
101
|
|
43
102
|
module ClassMethods
|
@@ -45,7 +104,8 @@ module DoorkeeperSequel
|
|
45
104
|
app = by_uid(uid)
|
46
105
|
return unless app
|
47
106
|
return app if secret.blank? && !app.confidential?
|
48
|
-
return unless app.secret
|
107
|
+
return unless app.secret_matches?(secret)
|
108
|
+
|
49
109
|
app
|
50
110
|
end
|
51
111
|
|
@@ -53,33 +113,64 @@ module DoorkeeperSequel
|
|
53
113
|
first(uid: uid.to_s)
|
54
114
|
end
|
55
115
|
|
56
|
-
def
|
57
|
-
|
116
|
+
def find_by(params)
|
117
|
+
first(params)
|
58
118
|
end
|
59
119
|
|
60
120
|
def column_names
|
61
121
|
columns.map(&:to_s)
|
62
122
|
end
|
123
|
+
|
124
|
+
def secret_strategy
|
125
|
+
::Doorkeeper.configuration.application_secret_strategy
|
126
|
+
end
|
127
|
+
|
128
|
+
def fallback_secret_strategy
|
129
|
+
::Doorkeeper.configuration.application_secret_fallback_strategy
|
130
|
+
end
|
63
131
|
end
|
64
132
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
return
|
133
|
+
def secret_matches?(input)
|
134
|
+
# return false if either is nil, since secure_compare depends on strings
|
135
|
+
# but Application secrets MAY be nil depending on confidentiality.
|
136
|
+
return false if input.nil? || secret.nil?
|
69
137
|
|
70
|
-
|
71
|
-
|
72
|
-
'Doorkeeper::CVE_2018_1000211_WARNING'
|
138
|
+
# When matching the secret by comparer function, all is well.
|
139
|
+
return true if secret_strategy.secret_matches?(input, secret)
|
73
140
|
|
74
|
-
|
141
|
+
# When fallback lookup is enabled, ensure applications
|
142
|
+
# with plain secrets can still be found
|
143
|
+
if fallback_secret_strategy
|
144
|
+
fallback_secret_strategy.secret_matches?(input, secret)
|
145
|
+
else
|
146
|
+
false
|
147
|
+
end
|
75
148
|
end
|
76
149
|
|
77
|
-
alias_method :confidential?, :confidential
|
78
|
-
|
79
150
|
private
|
80
|
-
|
151
|
+
|
152
|
+
def extract_serializable_attributes(options = {})
|
153
|
+
opts = options.try(:dup) || {}
|
154
|
+
only = Array.wrap(opts[:only]).map(&:to_s)
|
155
|
+
|
156
|
+
only = if only.blank?
|
157
|
+
serializable_attributes
|
158
|
+
else
|
159
|
+
only & serializable_attributes
|
160
|
+
end
|
161
|
+
|
162
|
+
only -= Array.wrap(opts[:except]).map(&:to_s) if opts.key?(:except)
|
163
|
+
only.uniq
|
164
|
+
end
|
165
|
+
|
166
|
+
def serializable_attributes
|
167
|
+
attributes = %w[id name created_at]
|
168
|
+
attributes << "uid" unless confidential?
|
169
|
+
attributes
|
170
|
+
end
|
171
|
+
|
81
172
|
def has_scopes?
|
82
|
-
Doorkeeper::Application.columns.include?(
|
173
|
+
Doorkeeper::Application.columns.include?("scopes")
|
83
174
|
end
|
84
175
|
|
85
176
|
def generate_uid
|
@@ -87,7 +178,10 @@ module DoorkeeperSequel
|
|
87
178
|
end
|
88
179
|
|
89
180
|
def generate_secret
|
90
|
-
|
181
|
+
return unless secret.blank?
|
182
|
+
|
183
|
+
@raw_secret = UniqueToken.generate
|
184
|
+
secret_strategy.store_secret(self, :secret, @raw_secret)
|
91
185
|
end
|
92
186
|
end
|
93
187
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DoorkeeperSequel
|
2
4
|
module SequelCompat
|
3
5
|
extend ActiveSupport::Concern
|
@@ -8,9 +10,7 @@ module DoorkeeperSequel
|
|
8
10
|
plugin :active_model
|
9
11
|
|
10
12
|
# Sequel 4.47 and higher deprecated #set_allowed_columns
|
11
|
-
if (::Sequel::MAJOR >= 4 && ::Sequel::MINOR >= 47) || ::Sequel::MAJOR >= 5
|
12
|
-
plugin :whitelist_security
|
13
|
-
end
|
13
|
+
plugin :whitelist_security if (::Sequel::MAJOR >= 4 && ::Sequel::MINOR >= 47) || ::Sequel::MAJOR >= 5
|
14
14
|
|
15
15
|
self.raise_on_save_failure = false
|
16
16
|
|
@@ -19,6 +19,8 @@ module DoorkeeperSequel
|
|
19
19
|
save(columns: [column.to_sym], validate: false)
|
20
20
|
end
|
21
21
|
|
22
|
+
alias_method :update_column, :update_attribute
|
23
|
+
|
22
24
|
def update_attributes(*args)
|
23
25
|
update(*args)
|
24
26
|
end
|
@@ -34,6 +36,26 @@ module DoorkeeperSequel
|
|
34
36
|
def invalid?
|
35
37
|
!valid?
|
36
38
|
end
|
39
|
+
|
40
|
+
def exists?
|
41
|
+
!empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def with_lock
|
45
|
+
return yield if @_tr_is_locked
|
46
|
+
@_tr_is_locked = true
|
47
|
+
|
48
|
+
begin
|
49
|
+
db.transaction do
|
50
|
+
lock!
|
51
|
+
yield
|
52
|
+
end
|
53
|
+
ensure
|
54
|
+
@_tr_is_locked = false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :exist?, :exists?
|
37
59
|
end
|
38
60
|
|
39
61
|
module ClassMethods
|
@@ -58,6 +80,14 @@ module DoorkeeperSequel
|
|
58
80
|
super(id: args)
|
59
81
|
end
|
60
82
|
end
|
83
|
+
|
84
|
+
def exists?(*args)
|
85
|
+
if args.any?
|
86
|
+
!where(*args).empty?
|
87
|
+
else
|
88
|
+
!empty?
|
89
|
+
end
|
90
|
+
end
|
61
91
|
end
|
62
92
|
end
|
63
93
|
end
|
@@ -1,18 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
namespace :doorkeeper_sequel do
|
2
4
|
namespace :generate do
|
3
|
-
desc
|
5
|
+
desc "Generate main migration file"
|
4
6
|
task :migration do
|
5
7
|
DoorkeeperSequel::MigrationGenerator.start
|
6
8
|
end
|
7
9
|
|
8
|
-
desc
|
10
|
+
desc "Generate migration file for Application Owner functionality"
|
9
11
|
task :application_owner do
|
10
12
|
DoorkeeperSequel::ApplicationOwnerGenerator.start
|
11
13
|
end
|
12
14
|
|
13
|
-
desc
|
15
|
+
desc "Generate migration file for Previous Refresh Token functionality"
|
14
16
|
task :previous_refresh_token do
|
15
17
|
DoorkeeperSequel::PreviousRefreshTokenGenerator.start
|
16
18
|
end
|
19
|
+
|
20
|
+
desc "Generate migration file for PKCE"
|
21
|
+
task :pkce do
|
22
|
+
DoorkeeperSequel::PkceGenerator.start
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "Add confidential column to Doorkeeper applications"
|
26
|
+
task :confidential_applications do
|
27
|
+
DoorkeeperSequel::ConfidentialApplicationsGenerator.start
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "Add resource owner type to Access tokens and grants"
|
31
|
+
task :polymorphic_resource_owner do
|
32
|
+
DoorkeeperSequel::PolymorphicResourceOwnerGenerator.start
|
33
|
+
end
|
17
34
|
end
|
18
35
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module DoorkeeperSequel
|
2
4
|
module RedirectUriValidator
|
3
5
|
extend ActiveSupport::Concern
|
@@ -6,12 +8,18 @@ module DoorkeeperSequel
|
|
6
8
|
def validates_redirect_uri(attribute)
|
7
9
|
value = self[attribute]
|
8
10
|
|
11
|
+
# Allow nil? but do not allow empty string
|
9
12
|
if value.blank?
|
10
|
-
|
13
|
+
if Doorkeeper.configuration.allow_blank_redirect_uri?(self)
|
14
|
+
true
|
15
|
+
else
|
16
|
+
add_error(attribute, :blank)
|
17
|
+
end
|
11
18
|
else
|
12
19
|
value.split.each do |val|
|
20
|
+
next if oob_redirect_uri?(val)
|
21
|
+
|
13
22
|
uri = ::URI.parse(val)
|
14
|
-
next if native_redirect_uri?(uri)
|
15
23
|
validate_uri(uri, attribute)
|
16
24
|
end
|
17
25
|
end
|
@@ -21,6 +29,10 @@ module DoorkeeperSequel
|
|
21
29
|
|
22
30
|
private
|
23
31
|
|
32
|
+
def oob_redirect_uri?(uri)
|
33
|
+
::Doorkeeper::OAuth::NonStandard::IETF_WG_OAUTH2_OOB_METHODS.include?(uri)
|
34
|
+
end
|
35
|
+
|
24
36
|
def native_redirect_uri?(uri)
|
25
37
|
native_redirect_uri.present? && uri.to_s == native_redirect_uri.to_s
|
26
38
|
end
|
@@ -33,17 +45,34 @@ module DoorkeeperSequel
|
|
33
45
|
def validate_uri(uri, attribute)
|
34
46
|
{
|
35
47
|
fragment_present: uri.fragment.present?,
|
36
|
-
relative_uri: uri.scheme.nil? || uri.host.nil?,
|
37
48
|
secured_uri: invalid_ssl_uri?(uri),
|
38
|
-
forbidden_uri: forbidden_uri?(uri)
|
49
|
+
forbidden_uri: forbidden_uri?(uri),
|
50
|
+
unspecified_scheme: unspecified_scheme?(uri),
|
51
|
+
unspecified_host: unspecified_host?(uri),
|
52
|
+
relative_uri: relative_uri?(uri),
|
39
53
|
}.each do |error, condition|
|
40
54
|
add_error(attribute, error) if condition
|
41
55
|
end
|
42
56
|
end
|
43
57
|
|
58
|
+
def unspecified_scheme?(uri)
|
59
|
+
return true if uri.opaque.present?
|
60
|
+
|
61
|
+
%w[localhost].include?(uri.try(:scheme))
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def unspecified_host?(uri)
|
66
|
+
uri.is_a?(URI::HTTP) && uri.host.nil?
|
67
|
+
end
|
68
|
+
|
69
|
+
def relative_uri?(uri)
|
70
|
+
uri.scheme.nil? && uri.host.nil?
|
71
|
+
end
|
72
|
+
|
44
73
|
def invalid_ssl_uri?(uri)
|
45
74
|
forces_ssl = Doorkeeper.configuration.force_ssl_in_redirect_uri
|
46
|
-
non_https = uri.try(:scheme) ==
|
75
|
+
non_https = uri.try(:scheme) == "http"
|
47
76
|
|
48
77
|
if forces_ssl.respond_to?(:call)
|
49
78
|
forces_ssl.call(uri) && non_https
|
@@ -57,7 +86,7 @@ module DoorkeeperSequel
|
|
57
86
|
end
|
58
87
|
|
59
88
|
def add_error(attribute, error)
|
60
|
-
scope =
|
89
|
+
scope = "sequel.errors.models.doorkeeper/application.attributes.redirect_uri"
|
61
90
|
errors.add(attribute, I18n.t(error, scope: scope))
|
62
91
|
end
|
63
92
|
end
|
@@ -1,3 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "doorkeeper/orm/sequel/stale_records_cleaner"
|
4
|
+
|
1
5
|
module Doorkeeper
|
2
6
|
module Orm
|
3
7
|
module Sequel
|
@@ -7,18 +11,21 @@ module Doorkeeper
|
|
7
11
|
# all the rake tasks (db:create, db:migrate, etc) would be aborted due to error.
|
8
12
|
old_value = ::Sequel::Model.require_valid_table
|
9
13
|
::Sequel::Model.require_valid_table = false
|
14
|
+
::Sequel::Model.strict_param_setting = false
|
15
|
+
::Sequel::Model.plugin :json_serializer
|
16
|
+
::Sequel::Model.plugin :polymorphic
|
10
17
|
|
11
18
|
begin
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
19
|
+
require "doorkeeper/orm/sequel/access_grant"
|
20
|
+
require "doorkeeper/orm/sequel/access_token"
|
21
|
+
require "doorkeeper/orm/sequel/application"
|
15
22
|
ensure
|
16
23
|
::Sequel::Model.require_valid_table = old_value
|
17
24
|
end
|
18
25
|
end
|
19
26
|
|
20
27
|
def self.initialize_application_owner!
|
21
|
-
require
|
28
|
+
require "doorkeeper-sequel/mixins/concerns/ownership"
|
22
29
|
|
23
30
|
Doorkeeper::Application.send :include, DoorkeeperSequel::Ownership
|
24
31
|
end
|