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.
Files changed (50) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +6 -0
  3. data/.gitignore +1 -0
  4. data/.gitmodules +0 -1
  5. data/.rubocop.yml +29 -2
  6. data/.travis.yml +3 -12
  7. data/CHANGELOG.md +13 -3
  8. data/Gemfile +13 -9
  9. data/README.md +18 -3
  10. data/Rakefile +22 -18
  11. data/config/locales/en.yml +11 -0
  12. data/doorkeeper-sequel.gemspec +27 -23
  13. data/gemfiles/rails-5.0.gemfile +9 -6
  14. data/gemfiles/rails-5.1.gemfile +9 -6
  15. data/gemfiles/rails-5.2.gemfile +9 -6
  16. data/gemfiles/rails-6.0.gemfile +14 -0
  17. data/lib/doorkeeper-sequel.rb +21 -17
  18. data/lib/doorkeeper-sequel/gem_version.rb +5 -3
  19. data/lib/doorkeeper-sequel/generators/application_owner_generator.rb +5 -3
  20. data/lib/doorkeeper-sequel/generators/concerns/migration_actions.rb +8 -6
  21. data/lib/doorkeeper-sequel/generators/confidential_applications_generator.rb +5 -3
  22. data/lib/doorkeeper-sequel/generators/migration_generator.rb +5 -3
  23. data/lib/doorkeeper-sequel/generators/pkce_generator.rb +16 -0
  24. data/lib/doorkeeper-sequel/generators/polymorphic_resource_owner_generator.rb +16 -0
  25. data/lib/doorkeeper-sequel/generators/previous_refresh_token_generator.rb +5 -3
  26. data/lib/doorkeeper-sequel/generators/templates/add_confidential_to_application_migration.rb +2 -0
  27. data/lib/doorkeeper-sequel/generators/templates/add_owner_to_application.rb +3 -1
  28. data/lib/doorkeeper-sequel/generators/templates/add_previous_refresh_token_to_access_tokens.rb +3 -1
  29. data/lib/doorkeeper-sequel/generators/templates/create_doorkeeper_tables.rb +7 -3
  30. data/lib/doorkeeper-sequel/generators/templates/enable_pkce_migration.rb +10 -0
  31. data/lib/doorkeeper-sequel/generators/templates/enable_polymorphic_resource_owner_migration.rb +15 -0
  32. data/lib/doorkeeper-sequel/mixins/access_grant_mixin.rb +45 -7
  33. data/lib/doorkeeper-sequel/mixins/access_token_mixin.rb +154 -42
  34. data/lib/doorkeeper-sequel/mixins/application_mixin.rb +116 -22
  35. data/lib/doorkeeper-sequel/mixins/concerns/ownership.rb +2 -0
  36. data/lib/doorkeeper-sequel/mixins/concerns/sequel_compat.rb +33 -3
  37. data/lib/doorkeeper-sequel/railtie.rb +3 -1
  38. data/lib/doorkeeper-sequel/tasks/doorkeeper-sequel.rake +20 -3
  39. data/lib/doorkeeper-sequel/validators/redirect_uri_validator.rb +35 -6
  40. data/lib/doorkeeper-sequel/version.rb +3 -1
  41. data/lib/doorkeeper/orm/sequel.rb +11 -4
  42. data/lib/doorkeeper/orm/sequel/access_grant.rb +18 -0
  43. data/lib/doorkeeper/orm/sequel/access_token.rb +6 -0
  44. data/lib/doorkeeper/orm/sequel/application.rb +34 -3
  45. data/lib/doorkeeper/orm/sequel/stale_records_cleaner.rb +26 -0
  46. data/lib/doorkeeper/redirect_uri_validator.rb +9 -0
  47. data/spec/stubs/config/initializers/db.rb +6 -1
  48. data/spec/stubs/spec_helper_integration.rb +9 -11
  49. metadata +225 -235
  50. data/gemfiles/rails-4.2.gemfile +0 -13
@@ -1,4 +1,6 @@
1
- require_relative '../validators/redirect_uri_validator'
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: 'Doorkeeper::AccessGrant'
18
- one_to_many :access_tokens, class: 'Doorkeeper::AccessToken'
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
- generate_uid
26
- generate_secret
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 [:name, :secret, :uid]
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 == 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 supports_confidentiality?
57
- column_names.include?('confidential')
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
- # Fallback to existing, default behaviour of assuming all apps to be
66
- # confidential if the migration hasn't been run
67
- def confidential
68
- return super if self.class.supports_confidentiality?
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
- ActiveSupport::Deprecation.warn 'You are susceptible to security bug ' \
71
- 'CVE-2018-1000211. Please follow instructions outlined in ' \
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
- true
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?('scopes')
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
- self.secret = UniqueToken.generate if secret.blank? && new?
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 Ownership
3
5
  extend ActiveSupport::Concern
@@ -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,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DoorkeeperSequel
2
4
  class Railtie < ::Rails::Railtie
3
5
  rake_tasks do
4
- load File.expand_path('../tasks/doorkeeper-sequel.rake', __FILE__)
6
+ load File.expand_path("tasks/doorkeeper-sequel.rake", __dir__)
5
7
  end
6
8
  end
7
9
  end
@@ -1,18 +1,35 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :doorkeeper_sequel do
2
4
  namespace :generate do
3
- desc 'Generate main migration file'
5
+ desc "Generate main migration file"
4
6
  task :migration do
5
7
  DoorkeeperSequel::MigrationGenerator.start
6
8
  end
7
9
 
8
- desc 'Generate migration file for Application Owner functionality'
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 'Generate migration file for Previous Refresh Token functionality'
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
- add_error(attribute, :blank)
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) == 'http'
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 = 'sequel.errors.models.doorkeeper/application.attributes.redirect_uri'
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,4 +1,6 @@
1
- require_relative 'gem_version'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "gem_version"
2
4
 
3
5
  module DoorkeeperSequel
4
6
  def self.version
@@ -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 'doorkeeper/orm/sequel/access_grant'
13
- require 'doorkeeper/orm/sequel/access_token'
14
- require 'doorkeeper/orm/sequel/application'
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 'doorkeeper-sequel/mixins/concerns/ownership'
28
+ require "doorkeeper-sequel/mixins/concerns/ownership"
22
29
 
23
30
  Doorkeeper::Application.send :include, DoorkeeperSequel::Ownership
24
31
  end