devise-security 0.12.0 → 0.16.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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +186 -63
  3. data/app/controllers/devise/paranoid_verification_code_controller.rb +2 -0
  4. data/app/controllers/devise/password_expired_controller.rb +13 -6
  5. data/app/views/devise/paranoid_verification_code/show.html.erb +4 -4
  6. data/app/views/devise/password_expired/show.html.erb +6 -6
  7. data/config/locales/by.yml +49 -0
  8. data/config/locales/cs.yml +41 -0
  9. data/config/locales/de.yml +30 -7
  10. data/config/locales/en.yml +25 -1
  11. data/config/locales/es.yml +19 -6
  12. data/config/locales/fa.yml +41 -0
  13. data/config/locales/fr.yml +30 -0
  14. data/config/locales/hi.yml +42 -0
  15. data/config/locales/it.yml +35 -4
  16. data/config/locales/ja.yml +30 -0
  17. data/config/locales/nl.yml +41 -0
  18. data/config/locales/pt.yml +41 -0
  19. data/config/locales/ru.yml +49 -0
  20. data/config/locales/tr.yml +18 -0
  21. data/config/locales/uk.yml +49 -0
  22. data/config/locales/zh_CN.yml +41 -0
  23. data/config/locales/zh_TW.yml +41 -0
  24. data/lib/devise-security/controllers/helpers.rb +61 -50
  25. data/lib/devise-security/hooks/expirable.rb +3 -1
  26. data/lib/devise-security/hooks/paranoid_verification.rb +2 -0
  27. data/lib/devise-security/hooks/password_expirable.rb +4 -0
  28. data/lib/devise-security/hooks/session_limitable.rb +31 -14
  29. data/lib/devise-security/models/active_record/old_password.rb +5 -0
  30. data/lib/devise-security/models/compatibility/active_record_patch.rb +40 -0
  31. data/lib/devise-security/models/compatibility/mongoid_patch.rb +31 -0
  32. data/lib/devise-security/models/compatibility.rb +8 -15
  33. data/lib/devise-security/models/database_authenticatable_patch.rb +3 -1
  34. data/lib/devise-security/models/expirable.rb +8 -2
  35. data/lib/devise-security/models/mongoid/old_password.rb +21 -0
  36. data/lib/devise-security/models/paranoid_verification.rb +2 -0
  37. data/lib/devise-security/models/password_archivable.rb +18 -7
  38. data/lib/devise-security/models/password_expirable.rb +103 -48
  39. data/lib/devise-security/models/secure_validatable.rb +26 -6
  40. data/lib/devise-security/models/security_questionable.rb +2 -0
  41. data/lib/devise-security/models/session_limitable.rb +19 -2
  42. data/lib/devise-security/orm/mongoid.rb +7 -0
  43. data/lib/devise-security/patches/confirmations_controller_captcha.rb +2 -0
  44. data/lib/devise-security/patches/confirmations_controller_security_question.rb +2 -0
  45. data/lib/devise-security/patches/controller_captcha.rb +2 -0
  46. data/lib/devise-security/patches/controller_security_question.rb +3 -1
  47. data/lib/devise-security/patches/passwords_controller_captcha.rb +2 -0
  48. data/lib/devise-security/patches/passwords_controller_security_question.rb +2 -0
  49. data/lib/devise-security/patches/registrations_controller_captcha.rb +2 -0
  50. data/lib/devise-security/patches/sessions_controller_captcha.rb +2 -0
  51. data/lib/devise-security/patches/unlocks_controller_captcha.rb +2 -0
  52. data/lib/devise-security/patches/unlocks_controller_security_question.rb +2 -0
  53. data/lib/devise-security/patches.rb +2 -0
  54. data/lib/devise-security/rails.rb +2 -0
  55. data/lib/devise-security/routes.rb +2 -0
  56. data/lib/devise-security/validators/password_complexity_validator.rb +35 -0
  57. data/lib/devise-security/version.rb +3 -1
  58. data/lib/devise-security.rb +16 -10
  59. data/lib/generators/devise_security/install_generator.rb +5 -3
  60. data/lib/generators/templates/devise_security.rb +47 -0
  61. data/test/{test_captcha_controller.rb → controllers/test_captcha_controller.rb} +2 -0
  62. data/test/controllers/test_password_expired_controller.rb +110 -0
  63. data/test/controllers/test_security_question_controller.rb +60 -0
  64. data/test/dummy/Rakefile +3 -1
  65. data/test/dummy/app/assets/config/manifest.js +3 -0
  66. data/test/dummy/app/controllers/application_controller.rb +2 -0
  67. data/test/dummy/app/controllers/captcha/sessions_controller.rb +2 -0
  68. data/test/dummy/app/controllers/security_question/unlocks_controller.rb +2 -0
  69. data/test/dummy/app/controllers/widgets_controller.rb +6 -0
  70. data/test/dummy/app/models/application_record.rb +10 -2
  71. data/test/dummy/app/models/application_user_record.rb +11 -0
  72. data/test/dummy/app/models/captcha_user.rb +7 -2
  73. data/test/dummy/app/models/mongoid/confirmable_fields.rb +13 -0
  74. data/test/dummy/app/models/mongoid/database_authenticable_fields.rb +17 -0
  75. data/test/dummy/app/models/mongoid/expirable_fields.rb +11 -0
  76. data/test/dummy/app/models/mongoid/lockable_fields.rb +13 -0
  77. data/test/dummy/app/models/mongoid/mappings.rb +13 -0
  78. data/test/dummy/app/models/mongoid/omniauthable_fields.rb +11 -0
  79. data/test/dummy/app/models/mongoid/paranoid_verification_fields.rb +10 -0
  80. data/test/dummy/app/models/mongoid/password_archivable_fields.rb +9 -0
  81. data/test/dummy/app/models/mongoid/password_expirable_fields.rb +10 -0
  82. data/test/dummy/app/models/mongoid/recoverable_fields.rb +11 -0
  83. data/test/dummy/app/models/mongoid/registerable_fields.rb +19 -0
  84. data/test/dummy/app/models/mongoid/rememberable_fields.rb +10 -0
  85. data/test/dummy/app/models/mongoid/secure_validatable_fields.rb +11 -0
  86. data/test/dummy/app/models/mongoid/security_questionable_fields.rb +13 -0
  87. data/test/dummy/app/models/mongoid/session_limitable_fields.rb +10 -0
  88. data/test/dummy/app/models/mongoid/timeoutable_fields.rb +9 -0
  89. data/test/dummy/app/models/mongoid/trackable_fields.rb +14 -0
  90. data/test/dummy/app/models/mongoid/validatable_fields.rb +7 -0
  91. data/test/dummy/app/models/secure_user.rb +7 -1
  92. data/test/dummy/app/models/security_question_user.rb +9 -4
  93. data/test/dummy/app/models/user.rb +15 -0
  94. data/test/dummy/app/models/widget.rb +6 -0
  95. data/test/dummy/app/mongoid/admin.rb +31 -0
  96. data/test/dummy/app/mongoid/one_user.rb +58 -0
  97. data/test/dummy/app/mongoid/shim.rb +25 -0
  98. data/test/dummy/app/mongoid/user_on_engine.rb +41 -0
  99. data/test/dummy/app/mongoid/user_on_main_app.rb +41 -0
  100. data/test/dummy/app/mongoid/user_with_validations.rb +37 -0
  101. data/test/dummy/app/mongoid/user_without_email.rb +35 -0
  102. data/test/dummy/config/application.rb +13 -7
  103. data/test/dummy/config/boot.rb +2 -0
  104. data/test/dummy/config/environment.rb +2 -0
  105. data/test/dummy/config/environments/test.rb +5 -13
  106. data/test/dummy/config/initializers/devise.rb +10 -3
  107. data/test/dummy/config/initializers/migration_class.rb +3 -6
  108. data/test/dummy/config/mongoid.yml +6 -0
  109. data/test/dummy/config/routes.rb +6 -3
  110. data/test/dummy/config.ru +3 -1
  111. data/test/dummy/db/migrate/20120508165529_create_tables.rb +13 -2
  112. data/test/dummy/db/migrate/20150402165590_add_verification_columns.rb +2 -0
  113. data/test/dummy/db/migrate/20150407162345_add_verification_attempt_column.rb +2 -0
  114. data/test/dummy/db/migrate/20160320162345_add_security_questions_fields.rb +2 -0
  115. data/test/dummy/db/migrate/20180318103603_add_expireable_columns.rb +2 -0
  116. data/test/dummy/db/migrate/20180318105329_add_confirmable_columns.rb +2 -0
  117. data/test/dummy/db/migrate/20180318105732_add_rememberable_columns.rb +2 -0
  118. data/test/dummy/db/migrate/20180318111336_add_recoverable_columns.rb +2 -0
  119. data/test/dummy/db/migrate/20180319114023_add_widget.rb +2 -0
  120. data/test/dummy/lib/shared_expirable_columns.rb +14 -0
  121. data/test/dummy/lib/shared_security_questions_fields.rb +16 -0
  122. data/test/dummy/lib/shared_user.rb +32 -0
  123. data/test/dummy/lib/shared_user_with_password_verification.rb +13 -0
  124. data/test/dummy/lib/shared_user_without_email.rb +28 -0
  125. data/test/dummy/lib/shared_user_without_omniauth.rb +15 -0
  126. data/test/dummy/lib/shared_verification_fields.rb +15 -0
  127. data/test/dummy/log/development.log +883 -0
  128. data/test/dummy/log/test.log +21689 -0
  129. data/test/integration/test_password_expirable_workflow.rb +53 -0
  130. data/test/integration/test_session_limitable_workflow.rb +67 -0
  131. data/test/orm/active_record.rb +15 -0
  132. data/test/orm/mongoid.rb +13 -0
  133. data/test/support/integration_helpers.rb +29 -0
  134. data/test/support/mongoid.yml +6 -0
  135. data/test/test_compatibility.rb +13 -0
  136. data/test/test_complexity_validator.rb +72 -0
  137. data/test/test_helper.rb +42 -9
  138. data/test/test_install_generator.rb +19 -2
  139. data/test/test_paranoid_verification.rb +2 -0
  140. data/test/test_password_archivable.rb +8 -7
  141. data/test/test_password_expirable.rb +70 -7
  142. data/test/test_secure_validatable.rb +97 -21
  143. data/test/test_session_limitable.rb +57 -0
  144. data/{lib/generators/templates → test/tmp/config/initializers}/devise-security.rb +12 -3
  145. data/test/tmp/config/locales/devise.security_extension.by.yml +49 -0
  146. data/test/tmp/config/locales/devise.security_extension.cs.yml +41 -0
  147. data/test/tmp/config/locales/devise.security_extension.de.yml +39 -0
  148. data/test/tmp/config/locales/devise.security_extension.en.yml +41 -0
  149. data/test/tmp/config/locales/devise.security_extension.es.yml +30 -0
  150. data/test/tmp/config/locales/devise.security_extension.fa.yml +41 -0
  151. data/test/tmp/config/locales/devise.security_extension.fr.yml +30 -0
  152. data/test/tmp/config/locales/devise.security_extension.hi.yml +42 -0
  153. data/test/tmp/config/locales/devise.security_extension.it.yml +41 -0
  154. data/test/tmp/config/locales/devise.security_extension.ja.yml +30 -0
  155. data/test/tmp/config/locales/devise.security_extension.nl.yml +41 -0
  156. data/test/tmp/config/locales/devise.security_extension.pt.yml +41 -0
  157. data/test/tmp/config/locales/devise.security_extension.ru.yml +49 -0
  158. data/test/tmp/config/locales/devise.security_extension.tr.yml +18 -0
  159. data/test/tmp/config/locales/devise.security_extension.uk.yml +49 -0
  160. data/test/tmp/config/locales/devise.security_extension.zh_CN.yml +41 -0
  161. data/test/tmp/config/locales/devise.security_extension.zh_TW.yml +41 -0
  162. metadata +235 -110
  163. data/.circleci/config.yml +0 -41
  164. data/.document +0 -5
  165. data/.gitignore +0 -40
  166. data/.rubocop.yml +0 -63
  167. data/.ruby-version +0 -1
  168. data/.travis.yml +0 -25
  169. data/Appraisals +0 -19
  170. data/Gemfile +0 -3
  171. data/Rakefile +0 -28
  172. data/devise-security.gemspec +0 -44
  173. data/gemfiles/rails_4.1_stable.gemfile +0 -8
  174. data/gemfiles/rails_4.2_stable.gemfile +0 -8
  175. data/gemfiles/rails_5.0_stable.gemfile +0 -8
  176. data/gemfiles/rails_5.1_stable.gemfile +0 -8
  177. data/gemfiles/rails_5.2_rc1.gemfile +0 -8
  178. data/lib/devise-security/models/old_password.rb +0 -4
  179. data/lib/devise-security/orm/active_record.rb +0 -18
  180. data/lib/devise-security/schema.rb +0 -64
  181. data/test/dummy/app/models/.gitkeep +0 -0
  182. data/test/test_password_expired_controller.rb +0 -44
  183. data/test/test_security_question_controller.rb +0 -84
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'compatibility'
4
+ require_relative '../validators/password_complexity_validator'
2
5
 
3
6
  module Devise
4
7
  module Models
@@ -45,11 +48,16 @@ module Devise
45
48
  end
46
49
 
47
50
  # extra validations
48
- validates :email, email: email_validation if email_validation # use rails_email_validator or similar
49
- validates :password, format: { with: password_regex, message: :password_format }, if: :password_required?
51
+ validates :email, email: email_validation if email_validation # see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
52
+ validates :password,
53
+ 'devise_security/password_complexity': password_complexity,
54
+ if: :password_required?
50
55
 
51
56
  # don't allow use same password
52
57
  validate :current_equal_password_validation
58
+
59
+ # don't allow email to equal password
60
+ validate :email_not_equal_password_validation unless allow_passwords_equal_to_email
53
61
  end
54
62
  end
55
63
 
@@ -65,6 +73,17 @@ module Devise
65
73
  self.errors.add(:password, :equal_to_current_password) if dummy.valid_password?(password)
66
74
  end
67
75
 
76
+ def email_not_equal_password_validation
77
+ return if password.blank? || (!new_record? && !will_save_change_to_encrypted_password?)
78
+ dummy = self.class.new.tap do |user|
79
+ user.password_salt = password_salt if respond_to?(:password_salt)
80
+ # whether case_insensitive_keys or strip_whitespace_keys include email or not, any
81
+ # variation of the email should not be a supported password
82
+ user.password = email.downcase.strip
83
+ end
84
+ self.errors.add(:password, :equal_to_email) if dummy.valid_password?(password.downcase.strip)
85
+ end
86
+
68
87
  protected
69
88
 
70
89
  # Checks whether a password is needed or not. For validations only.
@@ -79,13 +98,14 @@ module Devise
79
98
  end
80
99
 
81
100
  module ClassMethods
82
- Devise::Models.config(self, :password_regex, :password_length, :email_validation)
101
+ Devise::Models.config(self, :password_complexity, :password_length, :email_validation, :allow_passwords_equal_to_email)
102
+
103
+ private
83
104
 
84
- private
85
105
  def has_uniqueness_validation_of_login?
86
106
  validators.any? do |validator|
87
- validator.kind_of?(ActiveRecord::Validations::UniquenessValidator) &&
88
- validator.attributes.include?(login_attribute)
107
+ validator_orm_klass = DEVISE_ORM == :active_record ? ActiveRecord::Validations::UniquenessValidator : ::Mongoid::Validatable::UniquenessValidator
108
+ validator.kind_of?(validator_orm_klass) && validator.attributes.include?(login_attribute)
89
109
  end
90
110
  end
91
111
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # SecurityQuestionable is an accessible add-on for visually handicapped people,
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'compatibility'
1
4
  require 'devise-security/hooks/session_limitable'
2
5
 
3
6
  module Devise
@@ -9,13 +12,27 @@ module Devise
9
12
  # someone used his credentials to sign in.
10
13
  module SessionLimitable
11
14
  extend ActiveSupport::Concern
15
+ include Devise::Models::Compatibility
12
16
 
17
+ # Update the unique_session_id on the model. This will be checked in
18
+ # the Warden after_set_user hook in {file:devise-security/hooks/session_limitable}
19
+ # @param unique_session_id [String]
20
+ # @return [void]
21
+ # @raise [Devise::Models::Compatibility::NotPersistedError] if record is unsaved
13
22
  def update_unique_session_id!(unique_session_id)
14
- self.unique_session_id = unique_session_id
23
+ raise Devise::Models::Compatibility::NotPersistedError, 'cannot update a new record' unless persisted?
15
24
 
16
- save(validate: false)
25
+ update_attribute_without_validatons_or_callbacks(:unique_session_id, unique_session_id).tap do
26
+ Rails.logger.debug { "[devise-security][session_limitable] unique_session_id=#{unique_session_id}" }
27
+ end
17
28
  end
18
29
 
30
+ # Should session_limitable be skipped for this instance?
31
+ # @return [Boolean]
32
+ # @return [false] by default. This can be overridden by application logic as necessary.
33
+ def skip_session_limitable?
34
+ false
35
+ end
19
36
  end
20
37
  end
21
38
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ ActiveSupport.on_load(:mongoid) do
4
+ require 'orm_adapter/adapters/mongoid'
5
+
6
+ Mongoid::Document::ClassMethods.send :include, Devise::Models
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module ConfirmationsControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module ConfirmationsControllerSecurityQuestion
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module ControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module ControllerSecurityQuestion
3
5
  extend ActiveSupport::Concern
@@ -7,6 +9,7 @@ module DeviseSecurity::Patches
7
9
  end
8
10
 
9
11
  private
12
+
10
13
  def check_security_question
11
14
  # only find via email, not login
12
15
  resource = resource_class.find_or_initialize_with_error_by(:email, params[resource_name][:email], :not_found)
@@ -17,4 +20,3 @@ module DeviseSecurity::Patches
17
20
  end
18
21
  end
19
22
  end
20
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module PasswordsControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module PasswordsControllerSecurityQuestion
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module RegistrationsControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module SessionsControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module UnlocksControllerCaptcha
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity::Patches
2
4
  module UnlocksControllerSecurityQuestion
3
5
  extend ActiveSupport::Concern
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Patches
3
5
  autoload :ControllerCaptcha, 'devise-security/patches/controller_captcha'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  class Engine < ::Rails::Engine
3
5
  ActiveSupport.on_load(:action_controller) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionDispatch::Routing
2
4
  class Mapper
3
5
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Password complexity validator
4
+ # Options:
5
+ # - digit: minimum number of digits in the validated string
6
+ # - digits: minimum number of digits in the validated string
7
+ # - lower: minimum number of lower-case letters in the validated string
8
+ # - symbol: minimum number of punctuation characters or symbols in the validated string
9
+ # - symbols: minimum number of punctuation characters or symbols in the validated string
10
+ # - upper: minimum number of upper-case letters in the validated string
11
+ class DeviseSecurity::PasswordComplexityValidator < ActiveModel::EachValidator
12
+ PATTERNS = {
13
+ digit: /\p{Digit}/,
14
+ digits: /\p{Digit}/,
15
+ lower: /\p{Lower}/,
16
+ symbol: /\p{Punct}|\p{S}/,
17
+ symbols: /\p{Punct}|\p{S}/,
18
+ upper: /\p{Upper}/
19
+ }.freeze
20
+
21
+ def validate_each(record, attribute, value)
22
+ active_pattern_keys.each do |key|
23
+ minimum = [0, options[key].to_i].max
24
+ pattern = Regexp.new PATTERNS[key]
25
+
26
+ unless (value || '').scan(pattern).size >= minimum
27
+ record.errors.add attribute, :"password_complexity.#{key}", count: minimum
28
+ end
29
+ end
30
+ end
31
+
32
+ def active_pattern_keys
33
+ options.keys & PATTERNS.keys
34
+ end
35
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
- VERSION = '0.12.0'.freeze
4
+ VERSION = '0.16.0'
3
5
  end
@@ -1,18 +1,22 @@
1
- require 'active_record'
1
+ # frozen_string_literal: true
2
+ DEVISE_ORM = ENV.fetch('DEVISE_ORM', 'active_record').to_sym unless defined?(DEVISE_ORM)
3
+
4
+ require DEVISE_ORM.to_s if DEVISE_ORM.in? [:active_record, :mongoid]
2
5
  require 'active_support/core_ext/integer'
3
6
  require 'active_support/ordered_hash'
4
7
  require 'active_support/concern'
5
8
  require 'devise'
6
9
 
7
10
  module Devise
8
-
9
- # Should the password expire (e.g 3.months)
11
+ # Number of seconds that passwords are valid (e.g 3.months)
12
+ # Disable pasword expiration with +false+
13
+ # Expire only on demand with +true+
10
14
  mattr_accessor :expire_password_after
11
15
  @@expire_password_after = 3.months
12
16
 
13
17
  # Validate password for strongness
14
- mattr_accessor :password_regex
15
- @@password_regex = /(?=.*\d)(?=.*[a-z])(?=.*[A-Z])/
18
+ mattr_accessor :password_complexity
19
+ @@password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
16
20
 
17
21
  # Number of old passwords in archive
18
22
  mattr_accessor :password_archiving_count
@@ -23,7 +27,7 @@ module Devise
23
27
  @@deny_old_passwords = true
24
28
 
25
29
  # enable email validation for :secure_validatable. (true, false, validation_options)
26
- # dependency: need an email validator like rails_email_validator
30
+ # dependency: need an email validator, see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
27
31
  mattr_accessor :email_validation
28
32
  @@email_validation = true
29
33
 
@@ -75,11 +79,14 @@ module Devise
75
79
  # paranoid_verification will regenerate verifacation code after faild attempt
76
80
  mattr_accessor :paranoid_code_regenerate_after_attempt
77
81
  @@paranoid_code_regenerate_after_attempt = 10
82
+
83
+ # Whether to allow passwords that are equal (case insensitive) to the email
84
+ mattr_accessor :allow_passwords_equal_to_email
85
+ @@allow_passwords_equal_to_email = false
78
86
  end
79
87
 
80
- # an security extension for devise
88
+ # a security extension for devise
81
89
  module DeviseSecurity
82
- autoload :Schema, 'devise-security/schema'
83
90
  autoload :Patches, 'devise-security/patches'
84
91
 
85
92
  module Controllers
@@ -100,7 +107,6 @@ Devise.add_module :paranoid_verification, controller: :paranoid_verification_cod
100
107
  # requires
101
108
  require 'devise-security/routes'
102
109
  require 'devise-security/rails'
103
- require 'devise-security/orm/active_record'
104
- require 'devise-security/models/old_password'
110
+ require "devise-security/orm/#{DEVISE_ORM}" if DEVISE_ORM == :mongoid
105
111
  require 'devise-security/models/database_authenticatable_patch'
106
112
  require 'devise-security/models/paranoid_verification'
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DeviseSecurity
2
4
  module Generators
3
5
  # Generator for Rails to create or append to a Devise initializer.
4
6
  class InstallGenerator < Rails::Generators::Base
5
- LOCALES = %w[ en de it ]
7
+ LOCALES = %w[by cs de en es fa fr hi it ja nl pt ru tr uk zh_CN zh_TW].freeze
6
8
 
7
9
  source_root File.expand_path('../../templates', __FILE__)
8
10
  desc 'Install the devise security extension'
9
11
 
10
12
  def copy_initializer
11
- template('devise-security.rb',
12
- 'config/initializers/devise-security.rb',
13
+ template('devise_security.rb',
14
+ 'config/initializers/devise_security.rb',
13
15
  )
14
16
  end
15
17
 
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ Devise.setup do |config|
4
+ # ==> Security Extension
5
+ # Configure security extension for devise
6
+
7
+ # Should the password expire (e.g 3.months)
8
+ # config.expire_password_after = false
9
+
10
+ # Need 1 char of A-Z, a-z and 0-9
11
+ # config.password_complexity = { digit: 1, lower: 1, symbol: 1, upper: 1 }
12
+
13
+ # How many passwords to keep in archive
14
+ # config.password_archiving_count = 5
15
+
16
+ # Deny old passwords (true, false, number_of_old_passwords_to_check)
17
+ # Examples:
18
+ # config.deny_old_passwords = false # allow old passwords
19
+ # config.deny_old_passwords = true # will deny all the old passwords
20
+ # config.deny_old_passwords = 3 # will deny new passwords that matches with the last 3 passwords
21
+ # config.deny_old_passwords = true
22
+
23
+ # enable email validation for :secure_validatable. (true, false, validation_options)
24
+ # dependency: see https://github.com/devise-security/devise-security/blob/master/README.md#e-mail-validation
25
+ # config.email_validation = true
26
+
27
+ # captcha integration for recover form
28
+ # config.captcha_for_recover = true
29
+
30
+ # captcha integration for sign up form
31
+ # config.captcha_for_sign_up = true
32
+
33
+ # captcha integration for sign in form
34
+ # config.captcha_for_sign_in = true
35
+
36
+ # captcha integration for unlock form
37
+ # config.captcha_for_unlock = true
38
+
39
+ # captcha integration for confirmation form
40
+ # config.captcha_for_confirmation = true
41
+
42
+ # Time period for account expiry from last_activity_at
43
+ # config.expire_after = 90.days
44
+
45
+ # Allow password to equal the email
46
+ # config.allow_passwords_equal_to_email = false
47
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  class TestWithCaptcha < ActionController::TestCase
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class Devise::PasswordExpiredControllerTest < ActionController::TestCase
6
+ include Devise::Test::ControllerHelpers
7
+
8
+ setup do
9
+ @controller.class.respond_to :json, :xml
10
+ @request.env['devise.mapping'] = Devise.mappings[:user]
11
+ @user = User.create!(
12
+ username: 'hello',
13
+ email: 'hello@path.travel',
14
+ password: 'Password4',
15
+ password_changed_at: 4.months.ago,
16
+ confirmed_at: 5.months.ago,
17
+ )
18
+ assert @user.valid?
19
+ assert @user.need_change_password?
20
+
21
+ sign_in(@user)
22
+ end
23
+
24
+ test 'redirects on show if user not logged in' do
25
+ sign_out(@user)
26
+ get :show
27
+ assert_redirected_to :root
28
+ end
29
+
30
+ test 'redirects on show if user does not need password change' do
31
+ @user.update(password_changed_at: Time.zone.now)
32
+ get :show
33
+ assert_redirected_to :root
34
+ end
35
+
36
+ test 'should render show' do
37
+ get :show
38
+ assert_includes @response.body, 'Renew your password'
39
+ end
40
+
41
+ test 'redirects on update if user not logged in' do
42
+ sign_out(@user)
43
+ put :update
44
+ assert_redirected_to :root
45
+ end
46
+
47
+ test 'redirects on update if user does not need password change' do
48
+ @user.update(password_changed_at: Time.zone.now)
49
+ put :update
50
+ assert_redirected_to :root
51
+ end
52
+
53
+ test 'update password with default format' do
54
+ put :update,
55
+ params: {
56
+ user: {
57
+ current_password: 'Password4',
58
+ password: 'Password5',
59
+ password_confirmation: 'Password5',
60
+ },
61
+ }
62
+ assert_redirected_to root_path
63
+ assert_equal response.media_type, 'text/html'
64
+ end
65
+
66
+ test 'password confirmation does not match' do
67
+ put :update,
68
+ params: {
69
+ user: {
70
+ current_password: 'Password4',
71
+ password: 'Password5',
72
+ password_confirmation: 'Password6',
73
+ },
74
+ }
75
+
76
+ assert_response :success
77
+ assert_template :show
78
+ assert_equal response.media_type, 'text/html'
79
+ end
80
+
81
+ test 'update password using JSON format' do
82
+ put :update,
83
+ format: :json,
84
+ params: {
85
+ user: {
86
+ current_password: 'Password4',
87
+ password: 'Password5',
88
+ password_confirmation: 'Password5',
89
+ },
90
+ }
91
+ assert_response 204
92
+ assert_equal root_url, response.location
93
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
94
+ end
95
+
96
+ test 'update password using XML format' do
97
+ put :update,
98
+ format: :xml,
99
+ params: {
100
+ user: {
101
+ current_password: 'Password4',
102
+ password: 'Password5',
103
+ password_confirmation: 'Password5',
104
+ },
105
+ }
106
+ assert_response 204
107
+ assert_equal root_url, response.location
108
+ assert_nil response.media_type, 'No Content-Type header should be set for No Content response'
109
+ end
110
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class TestWithSecurityQuestion < ActionController::TestCase
6
+ include Devise::Test::ControllerHelpers
7
+ tests SecurityQuestion::UnlocksController
8
+
9
+ setup do
10
+ @user = SecurityQuestionUser.create!(username: 'hello', email: 'hello@microsoft.com',
11
+ password: 'A1234567z!', security_question_answer: 'Right Answer')
12
+ @user.lock_access!
13
+ assert @user.locked_at.present?
14
+ @request.env['devise.mapping'] = Devise.mappings[:security_question_user]
15
+ end
16
+
17
+ test 'When security question is enabled, it is inserted correctly' do
18
+ post :create, params: {
19
+ security_question_user: {
20
+ email: @user.email,
21
+ }, security_question_answer: 'wrong answer'
22
+ }
23
+ assert_equal I18n.t('devise.invalid_security_question'), flash[:alert]
24
+ assert_redirected_to new_security_question_user_unlock_path
25
+ end
26
+
27
+ test 'When security_question is valid, it runs as normal' do
28
+ post :create, params: {
29
+ security_question_user: {
30
+ email: @user.email,
31
+ }, security_question_answer: @user.security_question_answer
32
+ }
33
+
34
+ assert_equal I18n.t('devise.unlocks.send_instructions'), flash[:notice]
35
+ assert_redirected_to new_security_question_user_session_path
36
+ end
37
+ end
38
+
39
+ class TestWithoutSecurityQuestion < ActionController::TestCase
40
+ include Devise::Test::ControllerHelpers
41
+ tests Devise::UnlocksController
42
+
43
+ setup do
44
+ @user = User.create(username: 'hello', email: 'hello@path.travel',
45
+ password: '1234', security_question_answer: 'Right Answer')
46
+ @user.lock_access!
47
+ @request.env['devise.mapping'] = Devise.mappings[:user]
48
+ end
49
+
50
+ test 'When security question is not enabled it is not inserted' do
51
+ post :create, params: {
52
+ user: {
53
+ email: @user.email,
54
+ },
55
+ }
56
+
57
+ assert_equal I18n.t('devise.unlocks.send_instructions'), flash[:notice]
58
+ assert_redirected_to new_user_session_path
59
+ end
60
+ end
data/test/dummy/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
4
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
5
 
4
- require File.expand_path('../config/application', __FILE__)
6
+ require File.expand_path('config/application', __dir__)
5
7
 
6
8
  Rails.application.load_tasks
@@ -0,0 +1,3 @@
1
+ // = link_tree ../images
2
+ // = link_directory ../javascripts .js
3
+ // = link_directory ../stylesheets .css
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class ApplicationController < ActionController::Base
2
4
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Captcha::SessionsController < Devise::SessionsController
2
4
  include DeviseSecurity::Patches::ControllerCaptcha
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class SecurityQuestion::UnlocksController < Devise::UnlocksController
2
4
  include DeviseSecurity::Patches::ControllerSecurityQuestion
3
5
  end
@@ -0,0 +1,6 @@
1
+ class WidgetsController < ApplicationController
2
+ before_action :authenticate_user!
3
+ def show
4
+ render plain: 'success'
5
+ end
6
+ end
@@ -1,3 +1,11 @@
1
- class ApplicationRecord < ActiveRecord::Base
2
- self.abstract_class = true
1
+ # frozen_string_literal: true
2
+
3
+ if DEVISE_ORM == :active_record
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ else
8
+ class ApplicationRecord
9
+ include Mongoid::Document
10
+ end
3
11
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+ if DEVISE_ORM == :active_record
3
+ class ApplicationUserRecord < ActiveRecord::Base
4
+ self.table_name = 'users'
5
+ end
6
+ else
7
+ class ApplicationUserRecord
8
+ include Mongoid::Document
9
+ store_in collection: 'users'
10
+ end
11
+ end
@@ -1,5 +1,10 @@
1
- class CaptchaUser < ActiveRecord::Base
2
- self.table_name = 'users'
1
+ # frozen_string_literal: true
2
+
3
+ class CaptchaUser < ApplicationUserRecord
3
4
  devise :database_authenticatable, :password_archivable,
4
5
  :paranoid_verification, :password_expirable
6
+ if DEVISE_ORM == :mongoid
7
+ require './test/dummy/app/models/mongoid/mappings'
8
+ include ::Mongoid::Mappings
9
+ end
5
10
  end