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,10 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails_email_validator'
4
+ require "devise/orm/#{DEVISE_ORM}"
5
+
2
6
  Devise.setup do |config|
3
7
  config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
4
-
5
- require 'devise/orm/active_record'
6
8
  config.secret_key = 'f08cf11a38906f531d2dfc9a2c2d671aa0021be806c21255d4'
7
9
  config.case_insensitive_keys = [:email]
8
-
9
10
  config.strip_whitespace_keys = [:email]
11
+ config.password_complexity = {
12
+ digit: 1,
13
+ lower: 1,
14
+ upper: 1,
15
+ }
16
+ config.password_length = 7..128
10
17
  end
@@ -1,6 +1,3 @@
1
- MIGRATION_CLASS =
2
- if ActiveRecord::VERSION::MAJOR >= 5
3
- ActiveRecord::Migration[4.2]
4
- else
5
- ActiveRecord::Migration
6
- end
1
+ # frozen_string_literal: true
2
+
3
+ MIGRATION_CLASS = ActiveRecord::Migration[Rails.version.to_f] if DEVISE_ORM == :active_record
@@ -0,0 +1,6 @@
1
+ test:
2
+ clients:
3
+ default:
4
+ database: devise_security_test
5
+ hosts:
6
+ - localhost: <%= ENV.fetch('MONGODB_PORT', '27017') %>
@@ -1,10 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  RailsApp::Application.routes.draw do
2
4
  devise_for :users
3
5
 
4
- devise_for :captcha_users, only: [:sessions], controllers: { sessions: "captcha/sessions" }
5
- devise_for :security_question_users, only: [:sessions, :unlocks], controllers: { unlocks: "security_question/unlocks" }
6
+ devise_for :captcha_users, only: [:sessions], controllers: { sessions: 'captcha/sessions' }
7
+ devise_for :security_question_users, only: [:sessions, :unlocks], controllers: { unlocks: 'security_question/unlocks' }
6
8
 
7
9
  resources :foos
10
+ resource :widgets
8
11
 
9
- root to: 'foos#index'
12
+ root to: 'widgets#show'
10
13
  end
data/test/dummy/config.ru CHANGED
@@ -1,4 +1,6 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # This file is used by Rack-based servers to start the application.
2
4
 
3
- require ::File.expand_path('../config/environment', __FILE__)
5
+ require ::File.expand_path('../config/environment', __FILE__)
4
6
  run RailsApp::Application
@@ -1,15 +1,26 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class CreateTables < MIGRATION_CLASS
2
4
  def self.up
3
5
  create_table :users do |t|
4
6
  t.string :username
5
7
  t.string :facebook_token
6
- t.string :unique_session_id, :limit => 20
8
+
9
+ # session_limitable
10
+ t.string :unique_session_id
7
11
 
8
12
  ## Database authenticatable
9
13
  t.string :email, null: false, default: ''
10
14
  t.string :encrypted_password, null: false, default: ''
11
15
 
12
16
  t.datetime :password_changed_at
17
+
18
+ t.datetime :current_sign_in_at
19
+ t.datetime :last_sign_in_at
20
+ t.string :current_sign_in_ip
21
+ t.string :last_sign_in_ip
22
+ t.integer :sign_in_count, default: 0
23
+ t.integer :failed_attempts, default: 0
13
24
  t.timestamps null: false
14
25
  end
15
26
  add_index :users, :password_changed_at
@@ -28,7 +39,7 @@ class CreateTables < MIGRATION_CLASS
28
39
  t.integer :password_archivable_id, :null => false
29
40
  t.datetime :created_at
30
41
  end
31
- add_index :old_passwords, [:password_archivable_type, :password_archivable_id], :name => :index_password_archivable
42
+ add_index :old_passwords, [:password_archivable_type, :password_archivable_id], name: 'index_password_archivable'
32
43
  end
33
44
 
34
45
  def self.down
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddVerificationColumns < MIGRATION_CLASS
2
4
  def self.up
3
5
  add_column :users, :paranoid_verification_code, :string
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddVerificationAttemptColumn < MIGRATION_CLASS
2
4
  def self.up
3
5
  add_column :users, :paranoid_verification_attempt, :integer, default: 0
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddSecurityQuestionsFields < MIGRATION_CLASS
2
4
  def change
3
5
  add_column :users, :locked_at, :datetime
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddExpireableColumns < MIGRATION_CLASS
2
4
  def change
3
5
  add_column :users, :expired_at, :datetime
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddConfirmableColumns < MIGRATION_CLASS
2
4
  def change
3
5
  add_column :users, :confirmation_token, :string
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddRememberableColumns < MIGRATION_CLASS
2
4
  def change
3
5
  add_column :users, :remember_created_at, :datetime
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddRecoverableColumns < MIGRATION_CLASS
2
4
  def change
3
5
  add_column :users, :reset_password_token, :string
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AddWidget < MIGRATION_CLASS
2
4
  def change
3
5
  create_table :widgets do |t|
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ require 'shared_user'
3
+
4
+ module SharedVerificationColumns
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include SharedUser
9
+ devise :expirable
10
+
11
+ field :expired_at, type: Time
12
+ field :last_activity_at, type: Time
13
+ end
14
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ require 'shared_user'
3
+
4
+ module SharedSecurityQuestionsFields
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include SharedUser
9
+ devise :lockable, :security_questionable
10
+
11
+ field :locked_at, type: Time
12
+ field :unlock_token, type: String
13
+ field :security_question_id, type: Integer
14
+ field :security_question_answer, type: String
15
+ end
16
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedUser
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ devise :database_authenticatable, :confirmable, :lockable, :recoverable,
8
+ :registerable, :rememberable, :timeoutable,
9
+ :trackable, :secure_validatable, :omniauthable, :validatable, password_length: 7..72,
10
+ reconfirmable: false
11
+
12
+ attr_accessor :other_key
13
+
14
+ # They need to be included after Devise is called.
15
+ extend ExtendMethods
16
+ end
17
+
18
+ def raw_confirmation_token
19
+ @raw_confirmation_token
20
+ end
21
+
22
+ module ExtendMethods
23
+ def new_with_session(params, session)
24
+ super.tap do |user|
25
+ if data = session["devise.facebook_data"]
26
+ user.email = data["email"]
27
+ user.confirmed_at = Time.zone.now
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedUserWithPasswordVerification
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ include SharedVerificationFields
8
+ end
9
+
10
+ def raw_confirmation_token
11
+ @raw_confirmation_token
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedUserWithoutEmail
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # NOTE: This is missing :validatable and :confirmable, as they both require
8
+ # an email field at the moment. It is also missing :omniauthable because that
9
+ # adds unnecessary complexity to the setup
10
+ devise :database_authenticatable, :lockable, :recoverable,
11
+ :registerable, :rememberable, :timeoutable,
12
+ :trackable
13
+ end
14
+
15
+ # This test stub is a bit rubbish because it's tied very closely to the
16
+ # implementation where we care about this one case. However, completely
17
+ # removing the email field breaks "recoverable" tests completely, so we are
18
+ # just taking the approach here that "email" is something that is a not an
19
+ # ActiveRecord field.
20
+ def email_changed?
21
+ raise NoMethodError
22
+ end
23
+
24
+ def respond_to?(method_name, include_all=false)
25
+ return false if method_name.to_sym == :email_changed?
26
+ super(method_name, include_all)
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharedUserWithoutOmniauth
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ devise :database_authenticatable, :confirmable, :lockable, :recoverable,
8
+ :registerable, :rememberable, :timeoutable,
9
+ :trackable, :validatable, reconfirmable: false
10
+ end
11
+
12
+ def raw_confirmation_token
13
+ @raw_confirmation_token
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ require 'shared_user'
3
+
4
+ module SharedVerificationFields
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include SharedUser
9
+ devise :paranoid_verification
10
+
11
+ field :paranoid_verified_at, type: Time
12
+ field :paranoid_verification_attempt, type: Integer, default: 0
13
+ field :paranoid_verification_code, type: String
14
+ end
15
+ end