doorkeeper-sequel 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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