doorkeeper-sequel 2.0.0 → 2.1.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 (41) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +29 -2
  3. data/.travis.yml +3 -12
  4. data/Gemfile +13 -9
  5. data/Rakefile +22 -20
  6. data/config/locales/en.yml +8 -0
  7. data/doorkeeper-sequel.gemspec +27 -24
  8. data/gemfiles/rails-5.0.gemfile +9 -6
  9. data/gemfiles/rails-5.1.gemfile +9 -6
  10. data/gemfiles/rails-5.2.gemfile +9 -6
  11. data/gemfiles/rails-6.0.gemfile +18 -0
  12. data/lib/doorkeeper-sequel.rb +20 -17
  13. data/lib/doorkeeper-sequel/gem_version.rb +4 -2
  14. data/lib/doorkeeper-sequel/generators/application_owner_generator.rb +5 -3
  15. data/lib/doorkeeper-sequel/generators/concerns/migration_actions.rb +8 -6
  16. data/lib/doorkeeper-sequel/generators/confidential_applications_generator.rb +5 -3
  17. data/lib/doorkeeper-sequel/generators/migration_generator.rb +5 -3
  18. data/lib/doorkeeper-sequel/generators/pkce_generator.rb +5 -3
  19. data/lib/doorkeeper-sequel/generators/previous_refresh_token_generator.rb +5 -3
  20. data/lib/doorkeeper-sequel/generators/templates/add_confidential_to_application_migration.rb +2 -0
  21. data/lib/doorkeeper-sequel/generators/templates/add_owner_to_application.rb +3 -1
  22. data/lib/doorkeeper-sequel/generators/templates/add_previous_refresh_token_to_access_tokens.rb +3 -1
  23. data/lib/doorkeeper-sequel/generators/templates/create_doorkeeper_tables.rb +4 -2
  24. data/lib/doorkeeper-sequel/generators/templates/enable_pkce_migration.rb +3 -1
  25. data/lib/doorkeeper-sequel/mixins/access_grant_mixin.rb +30 -16
  26. data/lib/doorkeeper-sequel/mixins/access_token_mixin.rb +73 -24
  27. data/lib/doorkeeper-sequel/mixins/application_mixin.rb +52 -25
  28. data/lib/doorkeeper-sequel/mixins/concerns/ownership.rb +2 -0
  29. data/lib/doorkeeper-sequel/mixins/concerns/sequel_compat.rb +6 -4
  30. data/lib/doorkeeper-sequel/railtie.rb +3 -1
  31. data/lib/doorkeeper-sequel/tasks/doorkeeper-sequel.rake +7 -5
  32. data/lib/doorkeeper-sequel/validators/redirect_uri_validator.rb +29 -6
  33. data/lib/doorkeeper-sequel/version.rb +3 -1
  34. data/lib/doorkeeper/orm/sequel.rb +9 -5
  35. data/lib/doorkeeper/orm/sequel/access_grant.rb +18 -0
  36. data/lib/doorkeeper/orm/sequel/access_token.rb +2 -0
  37. data/lib/doorkeeper/orm/sequel/application.rb +26 -3
  38. data/lib/doorkeeper/redirect_uri_validator.rb +9 -0
  39. data/spec/stubs/spec_helper_integration.rb +8 -8
  40. metadata +177 -245
  41. 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,22 +17,24 @@ 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
@@ -41,13 +46,20 @@ module DoorkeeperSequel
41
46
  end
42
47
  end
43
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
+
44
57
  protected
45
58
 
46
59
  def validate_scopes_match_configured
47
- if scopes.present? &&
48
- !Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(scopes.to_s, Doorkeeper.configuration.scopes)
49
-
50
- scope = 'sequel.errors.models.doorkeeper/application.attributes.scopes'
60
+ if scopes.present? && !Doorkeeper::OAuth::Helpers::ScopeChecker.valid?(scope_str: scopes.to_s,
61
+ server_scopes: Doorkeeper.configuration.scopes)
62
+ scope = "sequel.errors.models.doorkeeper/application.attributes.scopes"
51
63
  errors.add(:scopes, I18n.t(:not_match_configured, scope: scope))
52
64
  end
53
65
  end
@@ -62,7 +74,8 @@ module DoorkeeperSequel
62
74
  app = by_uid(uid)
63
75
  return unless app
64
76
  return app if secret.blank? && !app.confidential?
65
- return unless app.secret == secret
77
+ return unless app.secret_matches?(secret)
78
+
66
79
  app
67
80
  end
68
81
 
@@ -70,33 +83,44 @@ module DoorkeeperSequel
70
83
  first(uid: uid.to_s)
71
84
  end
72
85
 
73
- def supports_confidentiality?
74
- column_names.include?('confidential')
86
+ def find_by(params)
87
+ first(params)
75
88
  end
76
89
 
77
90
  def column_names
78
91
  columns.map(&:to_s)
79
92
  end
93
+
94
+ def secret_strategy
95
+ ::Doorkeeper.configuration.application_secret_strategy
96
+ end
97
+
98
+ def fallback_secret_strategy
99
+ ::Doorkeeper.configuration.application_secret_fallback_strategy
100
+ end
80
101
  end
81
102
 
82
- # Fallback to existing, default behaviour of assuming all apps to be
83
- # confidential if the migration hasn't been run
84
- def confidential
85
- return super if self.class.supports_confidentiality?
103
+ def secret_matches?(input)
104
+ # return false if either is nil, since secure_compare depends on strings
105
+ # but Application secrets MAY be nil depending on confidentiality.
106
+ return false if input.nil? || secret.nil?
86
107
 
87
- ActiveSupport::Deprecation.warn 'You are susceptible to security bug ' \
88
- 'CVE-2018-1000211. Please follow instructions outlined in ' \
89
- 'Doorkeeper::CVE_2018_1000211_WARNING'
108
+ # When matching the secret by comparer function, all is well.
109
+ return true if secret_strategy.secret_matches?(input, secret)
90
110
 
91
- true
111
+ # When fallback lookup is enabled, ensure applications
112
+ # with plain secrets can still be found
113
+ if fallback_secret_strategy
114
+ fallback_secret_strategy.secret_matches?(input, secret)
115
+ else
116
+ false
117
+ end
92
118
  end
93
119
 
94
- alias_method :confidential?, :confidential
95
-
96
120
  private
97
121
 
98
122
  def has_scopes?
99
- Doorkeeper::Application.columns.include?('scopes')
123
+ Doorkeeper::Application.columns.include?("scopes")
100
124
  end
101
125
 
102
126
  def generate_uid
@@ -104,7 +128,10 @@ module DoorkeeperSequel
104
128
  end
105
129
 
106
130
  def generate_secret
107
- self.secret = UniqueToken.generate if secret.blank? && new?
131
+ return unless secret.blank?
132
+
133
+ @raw_secret = UniqueToken.generate
134
+ secret_strategy.store_secret(self, :secret, @raw_secret)
108
135
  end
109
136
  end
110
137
  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
@@ -36,7 +38,7 @@ module DoorkeeperSequel
36
38
  end
37
39
 
38
40
  def exists?
39
- !self.empty?
41
+ !empty?
40
42
  end
41
43
 
42
44
  alias_method :exist?, :exists?
@@ -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,26 +1,28 @@
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
17
19
 
18
- desc 'Generate migration file for PKCE'
20
+ desc "Generate migration file for PKCE"
19
21
  task :pkce do
20
22
  DoorkeeperSequel::PkceGenerator.start
21
23
  end
22
24
 
23
- desc 'Add confidential column to Doorkeeper applications'
25
+ desc "Add confidential column to Doorkeeper applications"
24
26
  task :confidential_applications do
25
27
  DoorkeeperSequel::ConfidentialApplicationsGenerator.start
26
28
  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,28 @@ 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
+ relative_uri: relative_uri?(uri),
39
52
  }.each do |error, condition|
40
53
  add_error(attribute, error) if condition
41
54
  end
42
55
  end
43
56
 
57
+ def unspecified_scheme?(uri)
58
+ return true if uri.opaque.present?
59
+
60
+ %w[localhost].include?(uri.try(:scheme))
61
+ end
62
+
63
+ def relative_uri?(uri)
64
+ uri.scheme.nil? && uri.host.nil?
65
+ end
66
+
44
67
  def invalid_ssl_uri?(uri)
45
68
  forces_ssl = Doorkeeper.configuration.force_ssl_in_redirect_uri
46
- non_https = uri.try(:scheme) == 'http'
69
+ non_https = uri.try(:scheme) == "http"
47
70
 
48
71
  if forces_ssl.respond_to?(:call)
49
72
  forces_ssl.call(uri) && non_https
@@ -57,7 +80,7 @@ module DoorkeeperSequel
57
80
  end
58
81
 
59
82
  def add_error(attribute, error)
60
- scope = 'sequel.errors.models.doorkeeper/application.attributes.redirect_uri'
83
+ scope = "sequel.errors.models.doorkeeper/application.attributes.redirect_uri"
61
84
  errors.add(attribute, I18n.t(error, scope: scope))
62
85
  end
63
86
  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,4 +1,6 @@
1
- require 'doorkeeper/orm/sequel/stale_records_cleaner'
1
+ # frozen_string_literal: true
2
+
3
+ require "doorkeeper/orm/sequel/stale_records_cleaner"
2
4
 
3
5
  module Doorkeeper
4
6
  module Orm
@@ -9,18 +11,20 @@ module Doorkeeper
9
11
  # all the rake tasks (db:create, db:migrate, etc) would be aborted due to error.
10
12
  old_value = ::Sequel::Model.require_valid_table
11
13
  ::Sequel::Model.require_valid_table = false
14
+ ::Sequel::Model.strict_param_setting = false
15
+ ::Sequel::Model.plugin :json_serializer
12
16
 
13
17
  begin
14
- require 'doorkeeper/orm/sequel/access_grant'
15
- require 'doorkeeper/orm/sequel/access_token'
16
- require 'doorkeeper/orm/sequel/application'
18
+ require "doorkeeper/orm/sequel/access_grant"
19
+ require "doorkeeper/orm/sequel/access_token"
20
+ require "doorkeeper/orm/sequel/application"
17
21
  ensure
18
22
  ::Sequel::Model.require_valid_table = old_value
19
23
  end
20
24
  end
21
25
 
22
26
  def self.initialize_application_owner!
23
- require 'doorkeeper-sequel/mixins/concerns/ownership'
27
+ require "doorkeeper-sequel/mixins/concerns/ownership"
24
28
 
25
29
  Doorkeeper::Application.send :include, DoorkeeperSequel::Ownership
26
30
  end
@@ -1,5 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  class AccessGrant < Sequel::Model(:oauth_access_grants)
3
5
  include DoorkeeperSequel::AccessGrantMixin
6
+
7
+ def plaintext_token
8
+ if secret_strategy.allows_restoring_secrets?
9
+ secret_strategy.restore_secret(self, :token)
10
+ else
11
+ @raw_token
12
+ end
13
+ end
14
+
15
+ def uses_pkce?
16
+ pkce_supported? && code_challenge.present?
17
+ end
18
+
19
+ def pkce_supported?
20
+ respond_to?(:code_challenge)
21
+ end
4
22
  end
5
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  class AccessToken < Sequel::Model(:oauth_access_tokens)
3
5
  include DoorkeeperSequel::AccessTokenMixin
@@ -1,15 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Doorkeeper
2
4
  class Application < Sequel::Model(:oauth_applications)
3
5
  include DoorkeeperSequel::ApplicationMixin
4
6
 
5
- one_to_many :authorized_tokens, class: 'Doorkeeper::AccessToken', conditions: { revoked_at: nil }
6
- many_to_many :authorized_applications, join_table: :oauth_access_tokens,
7
- class: self, left_key: :id, right_key: :application_id
7
+ one_to_many :authorized_tokens,
8
+ class: "Doorkeeper::AccessToken",
9
+ conditions: { revoked_at: nil }
10
+
11
+ many_to_many :authorized_applications,
12
+ join_table: :oauth_access_tokens,
13
+ class: self,
14
+ left_key: :id,
15
+ right_key: :application_id
8
16
 
9
17
  def redirect_uri=(uris)
10
18
  super(uris.is_a?(Array) ? uris.join("\n") : uris)
11
19
  end
12
20
 
21
+ def plaintext_secret
22
+ if secret_strategy.allows_restoring_secrets?
23
+ secret_strategy.restore_secret(self, :secret)
24
+ else
25
+ @raw_secret
26
+ end
27
+ end
28
+
29
+ def to_json(options = nil)
30
+ hash = to_hash.dup
31
+ hash.delete(:secret)
32
+ hash.merge(secret: plaintext_secret)
33
+ .to_json(options)
34
+ end
35
+
13
36
  def self.authorized_for(resource_owner)
14
37
  resource_access_tokens = AccessToken.active_for(resource_owner)
15
38
  where(id: resource_access_tokens.select_map(:application_id)).all
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "doorkeeper-sequel/validators/redirect_uri_validator"
4
+
5
+ module Doorkeeper
6
+ module RedirectUriValidator
7
+ extend DoorkeeperSequel::RedirectUriValidator
8
+ end
9
+ end
@@ -1,17 +1,17 @@
1
- ENV['RAILS_ENV'] ||= 'test'
1
+ ENV["RAILS_ENV"] ||= "test"
2
2
 
3
3
  DOORKEEPER_ORM = :sequel
4
4
 
5
5
  $LOAD_PATH.unshift File.dirname(__FILE__)
6
6
 
7
- require 'capybara/rspec'
8
- require 'dummy/config/environment'
9
- require 'rspec/rails'
10
- require 'generator_spec/test_case'
7
+ require "capybara/rspec"
8
+ require "dummy/config/environment"
9
+ require "rspec/rails"
10
+ require "generator_spec/test_case"
11
11
 
12
12
  # Load JRuby SQLite3 if in that platform
13
13
  begin
14
- require 'jdbc/sqlite3'
14
+ require "jdbc/sqlite3"
15
15
  Jdbc::SQLite3.load_driver
16
16
  rescue LoadError
17
17
  end
@@ -22,7 +22,7 @@ Rails.logger.info "====> Ruby version: #{RUBY_VERSION}"
22
22
 
23
23
  require "support/orm/#{DOORKEEPER_ORM}"
24
24
 
25
- ENGINE_RAILS_ROOT = File.join(File.dirname(__FILE__), '../')
25
+ ENGINE_RAILS_ROOT = File.join(File.dirname(__FILE__), "../")
26
26
 
27
27
  Dir["#{File.dirname(__FILE__)}/support/{dependencies,helpers,shared}/*.rb"].each { |f| require f }
28
28
 
@@ -45,5 +45,5 @@ RSpec.configure do |config|
45
45
  DB.transaction(rollback: :always, auto_savepoint: true) { example.run }
46
46
  end
47
47
 
48
- config.order = 'random'
48
+ config.order = "random"
49
49
  end