devise 3.5.10 → 4.8.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 (258) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +279 -1126
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +291 -97
  5. data/app/controllers/devise/confirmations_controller.rb +3 -1
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +8 -6
  7. data/app/controllers/devise/passwords_controller.rb +7 -4
  8. data/app/controllers/devise/registrations_controller.rb +39 -18
  9. data/app/controllers/devise/sessions_controller.rb +9 -7
  10. data/app/controllers/devise/unlocks_controller.rb +4 -2
  11. data/app/controllers/devise_controller.rb +25 -12
  12. data/app/helpers/devise_helper.rb +23 -18
  13. data/app/mailers/devise/mailer.rb +10 -4
  14. data/app/views/devise/confirmations/new.html.erb +2 -2
  15. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  16. data/app/views/devise/passwords/edit.html.erb +3 -3
  17. data/app/views/devise/passwords/new.html.erb +2 -2
  18. data/app/views/devise/registrations/edit.html.erb +9 -5
  19. data/app/views/devise/registrations/new.html.erb +4 -4
  20. data/app/views/devise/sessions/new.html.erb +4 -4
  21. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  22. data/app/views/devise/shared/_links.html.erb +8 -8
  23. data/app/views/devise/unlocks/new.html.erb +2 -2
  24. data/config/locales/en.yml +5 -2
  25. data/lib/devise.rb +57 -40
  26. data/lib/devise/controllers/helpers.rb +30 -27
  27. data/lib/devise/controllers/rememberable.rb +3 -1
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +39 -14
  30. data/lib/devise/controllers/store_location.rb +25 -7
  31. data/lib/devise/controllers/url_helpers.rb +3 -1
  32. data/lib/devise/delegator.rb +2 -0
  33. data/lib/devise/encryptor.rb +6 -4
  34. data/lib/devise/failure_app.rb +75 -38
  35. data/lib/devise/hooks/activatable.rb +2 -0
  36. data/lib/devise/hooks/csrf_cleaner.rb +2 -0
  37. data/lib/devise/hooks/forgetable.rb +2 -0
  38. data/lib/devise/hooks/lockable.rb +4 -2
  39. data/lib/devise/hooks/proxy.rb +3 -1
  40. data/lib/devise/hooks/rememberable.rb +2 -0
  41. data/lib/devise/hooks/timeoutable.rb +4 -2
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +7 -4
  44. data/lib/devise/mapping.rb +3 -1
  45. data/lib/devise/models.rb +3 -1
  46. data/lib/devise/models/authenticatable.rb +63 -33
  47. data/lib/devise/models/confirmable.rb +90 -29
  48. data/lib/devise/models/database_authenticatable.rb +93 -22
  49. data/lib/devise/models/lockable.rb +19 -5
  50. data/lib/devise/models/omniauthable.rb +2 -0
  51. data/lib/devise/models/recoverable.rb +33 -21
  52. data/lib/devise/models/registerable.rb +4 -0
  53. data/lib/devise/models/rememberable.rb +6 -11
  54. data/lib/devise/models/timeoutable.rb +2 -0
  55. data/lib/devise/models/trackable.rb +15 -1
  56. data/lib/devise/models/validatable.rb +10 -3
  57. data/lib/devise/modules.rb +2 -0
  58. data/lib/devise/omniauth.rb +4 -5
  59. data/lib/devise/omniauth/config.rb +2 -0
  60. data/lib/devise/omniauth/url_helpers.rb +14 -5
  61. data/lib/devise/orm/active_record.rb +5 -1
  62. data/lib/devise/orm/mongoid.rb +6 -2
  63. data/lib/devise/parameter_filter.rb +4 -0
  64. data/lib/devise/parameter_sanitizer.rb +139 -65
  65. data/lib/devise/rails.rb +7 -16
  66. data/lib/devise/rails/deprecated_constant_accessor.rb +39 -0
  67. data/lib/devise/rails/routes.rb +48 -37
  68. data/lib/devise/rails/warden_compat.rb +3 -10
  69. data/lib/devise/secret_key_finder.rb +27 -0
  70. data/lib/devise/strategies/authenticatable.rb +3 -1
  71. data/lib/devise/strategies/base.rb +2 -0
  72. data/lib/devise/strategies/database_authenticatable.rb +11 -4
  73. data/lib/devise/strategies/rememberable.rb +2 -0
  74. data/lib/devise/test/controller_helpers.rb +167 -0
  75. data/lib/devise/test/integration_helpers.rb +63 -0
  76. data/lib/devise/test_helpers.rb +7 -124
  77. data/lib/devise/time_inflector.rb +2 -0
  78. data/lib/devise/token_generator.rb +3 -41
  79. data/lib/devise/version.rb +3 -1
  80. data/lib/generators/active_record/devise_generator.rb +46 -12
  81. data/lib/generators/active_record/templates/migration.rb +4 -2
  82. data/lib/generators/active_record/templates/migration_existing.rb +4 -2
  83. data/lib/generators/devise/controllers_generator.rb +3 -1
  84. data/lib/generators/devise/devise_generator.rb +5 -3
  85. data/lib/generators/devise/install_generator.rb +18 -5
  86. data/lib/generators/devise/orm_helpers.rb +10 -21
  87. data/lib/generators/devise/views_generator.rb +8 -9
  88. data/lib/generators/mongoid/devise_generator.rb +7 -5
  89. data/lib/generators/templates/README +9 -8
  90. data/lib/generators/templates/controllers/confirmations_controller.rb +2 -0
  91. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +3 -1
  92. data/lib/generators/templates/controllers/passwords_controller.rb +2 -0
  93. data/lib/generators/templates/controllers/registrations_controller.rb +6 -4
  94. data/lib/generators/templates/controllers/sessions_controller.rb +4 -2
  95. data/lib/generators/templates/controllers/unlocks_controller.rb +2 -0
  96. data/lib/generators/templates/devise.rb +63 -21
  97. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  98. data/lib/generators/templates/markerb/password_change.markerb +2 -2
  99. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  100. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  101. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +4 -1
  102. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -3
  103. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  104. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +7 -2
  105. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  106. metadata +19 -317
  107. data/.gitignore +0 -10
  108. data/.travis.yml +0 -44
  109. data/.yardopts +0 -9
  110. data/CODE_OF_CONDUCT.md +0 -22
  111. data/CONTRIBUTING.md +0 -16
  112. data/Gemfile +0 -30
  113. data/Gemfile.lock +0 -187
  114. data/Rakefile +0 -36
  115. data/devise.gemspec +0 -27
  116. data/devise.png +0 -0
  117. data/gemfiles/Gemfile.rails-3.2-stable +0 -29
  118. data/gemfiles/Gemfile.rails-3.2-stable.lock +0 -172
  119. data/gemfiles/Gemfile.rails-4.0-stable +0 -30
  120. data/gemfiles/Gemfile.rails-4.0-stable.lock +0 -166
  121. data/gemfiles/Gemfile.rails-4.1-stable +0 -30
  122. data/gemfiles/Gemfile.rails-4.1-stable.lock +0 -171
  123. data/gemfiles/Gemfile.rails-4.2-stable +0 -30
  124. data/gemfiles/Gemfile.rails-4.2-stable.lock +0 -193
  125. data/script/cached-bundle +0 -49
  126. data/script/s3-put +0 -71
  127. data/test/controllers/custom_registrations_controller_test.rb +0 -40
  128. data/test/controllers/custom_strategy_test.rb +0 -62
  129. data/test/controllers/helper_methods_test.rb +0 -21
  130. data/test/controllers/helpers_test.rb +0 -316
  131. data/test/controllers/inherited_controller_i18n_messages_test.rb +0 -51
  132. data/test/controllers/internal_helpers_test.rb +0 -129
  133. data/test/controllers/load_hooks_controller_test.rb +0 -19
  134. data/test/controllers/passwords_controller_test.rb +0 -31
  135. data/test/controllers/sessions_controller_test.rb +0 -103
  136. data/test/controllers/url_helpers_test.rb +0 -65
  137. data/test/delegator_test.rb +0 -19
  138. data/test/devise_test.rb +0 -107
  139. data/test/failure_app_test.rb +0 -315
  140. data/test/generators/active_record_generator_test.rb +0 -109
  141. data/test/generators/controllers_generator_test.rb +0 -48
  142. data/test/generators/devise_generator_test.rb +0 -39
  143. data/test/generators/install_generator_test.rb +0 -13
  144. data/test/generators/mongoid_generator_test.rb +0 -23
  145. data/test/generators/views_generator_test.rb +0 -103
  146. data/test/helpers/devise_helper_test.rb +0 -49
  147. data/test/integration/authenticatable_test.rb +0 -729
  148. data/test/integration/confirmable_test.rb +0 -324
  149. data/test/integration/database_authenticatable_test.rb +0 -95
  150. data/test/integration/http_authenticatable_test.rb +0 -105
  151. data/test/integration/lockable_test.rb +0 -239
  152. data/test/integration/omniauthable_test.rb +0 -135
  153. data/test/integration/recoverable_test.rb +0 -347
  154. data/test/integration/registerable_test.rb +0 -359
  155. data/test/integration/rememberable_test.rb +0 -214
  156. data/test/integration/timeoutable_test.rb +0 -184
  157. data/test/integration/trackable_test.rb +0 -92
  158. data/test/mailers/confirmation_instructions_test.rb +0 -115
  159. data/test/mailers/reset_password_instructions_test.rb +0 -96
  160. data/test/mailers/unlock_instructions_test.rb +0 -91
  161. data/test/mapping_test.rb +0 -134
  162. data/test/models/authenticatable_test.rb +0 -23
  163. data/test/models/confirmable_test.rb +0 -511
  164. data/test/models/database_authenticatable_test.rb +0 -269
  165. data/test/models/lockable_test.rb +0 -350
  166. data/test/models/omniauthable_test.rb +0 -7
  167. data/test/models/recoverable_test.rb +0 -251
  168. data/test/models/registerable_test.rb +0 -7
  169. data/test/models/rememberable_test.rb +0 -169
  170. data/test/models/serializable_test.rb +0 -49
  171. data/test/models/timeoutable_test.rb +0 -51
  172. data/test/models/trackable_test.rb +0 -41
  173. data/test/models/validatable_test.rb +0 -127
  174. data/test/models_test.rb +0 -153
  175. data/test/omniauth/config_test.rb +0 -57
  176. data/test/omniauth/url_helpers_test.rb +0 -54
  177. data/test/orm/active_record.rb +0 -10
  178. data/test/orm/mongoid.rb +0 -13
  179. data/test/parameter_sanitizer_test.rb +0 -81
  180. data/test/rails_app/Rakefile +0 -6
  181. data/test/rails_app/app/active_record/admin.rb +0 -6
  182. data/test/rails_app/app/active_record/shim.rb +0 -2
  183. data/test/rails_app/app/active_record/user.rb +0 -6
  184. data/test/rails_app/app/active_record/user_on_engine.rb +0 -7
  185. data/test/rails_app/app/active_record/user_on_main_app.rb +0 -7
  186. data/test/rails_app/app/active_record/user_without_email.rb +0 -8
  187. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  188. data/test/rails_app/app/controllers/admins_controller.rb +0 -6
  189. data/test/rails_app/app/controllers/application_controller.rb +0 -12
  190. data/test/rails_app/app/controllers/application_with_fake_engine.rb +0 -30
  191. data/test/rails_app/app/controllers/custom/registrations_controller.rb +0 -31
  192. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  193. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  194. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  195. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  196. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  197. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  198. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +0 -3
  199. data/test/rails_app/app/mailers/users/mailer.rb +0 -3
  200. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +0 -4
  201. data/test/rails_app/app/mongoid/admin.rb +0 -29
  202. data/test/rails_app/app/mongoid/shim.rb +0 -23
  203. data/test/rails_app/app/mongoid/user.rb +0 -39
  204. data/test/rails_app/app/mongoid/user_on_engine.rb +0 -39
  205. data/test/rails_app/app/mongoid/user_on_main_app.rb +0 -39
  206. data/test/rails_app/app/mongoid/user_without_email.rb +0 -33
  207. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  208. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  209. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  210. data/test/rails_app/app/views/home/index.html.erb +0 -1
  211. data/test/rails_app/app/views/home/join.html.erb +0 -1
  212. data/test/rails_app/app/views/home/private.html.erb +0 -1
  213. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  214. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  215. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  216. data/test/rails_app/app/views/users/index.html.erb +0 -1
  217. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  218. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  219. data/test/rails_app/bin/bundle +0 -3
  220. data/test/rails_app/bin/rails +0 -4
  221. data/test/rails_app/bin/rake +0 -4
  222. data/test/rails_app/config.ru +0 -4
  223. data/test/rails_app/config/application.rb +0 -40
  224. data/test/rails_app/config/boot.rb +0 -14
  225. data/test/rails_app/config/database.yml +0 -18
  226. data/test/rails_app/config/environment.rb +0 -5
  227. data/test/rails_app/config/environments/development.rb +0 -30
  228. data/test/rails_app/config/environments/production.rb +0 -84
  229. data/test/rails_app/config/environments/test.rb +0 -41
  230. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  231. data/test/rails_app/config/initializers/devise.rb +0 -180
  232. data/test/rails_app/config/initializers/inflections.rb +0 -2
  233. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  234. data/test/rails_app/config/initializers/session_store.rb +0 -1
  235. data/test/rails_app/config/routes.rb +0 -125
  236. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  237. data/test/rails_app/db/schema.rb +0 -55
  238. data/test/rails_app/lib/shared_admin.rb +0 -17
  239. data/test/rails_app/lib/shared_user.rb +0 -29
  240. data/test/rails_app/lib/shared_user_without_email.rb +0 -26
  241. data/test/rails_app/lib/shared_user_without_omniauth.rb +0 -13
  242. data/test/rails_app/public/404.html +0 -26
  243. data/test/rails_app/public/422.html +0 -26
  244. data/test/rails_app/public/500.html +0 -26
  245. data/test/rails_app/public/favicon.ico +0 -0
  246. data/test/rails_test.rb +0 -9
  247. data/test/routes_test.rb +0 -264
  248. data/test/support/action_controller/record_identifier.rb +0 -10
  249. data/test/support/assertions.rb +0 -39
  250. data/test/support/helpers.rb +0 -77
  251. data/test/support/integration.rb +0 -92
  252. data/test/support/locale/en.yml +0 -8
  253. data/test/support/mongoid.yml +0 -6
  254. data/test/support/webrat/integrations/rails.rb +0 -24
  255. data/test/test_helper.rb +0 -34
  256. data/test/test_helpers_test.rb +0 -178
  257. data/test/test_models.rb +0 -33
  258. data/test/time_helpers.rb +0 -137
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Deny user access whenever their account is not active yet.
2
4
  # We need this as hook to validate the user activity on each request
3
5
  # and in case the user is using other strategies beside Devise ones.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Warden::Manager.after_authentication do |record, warden, options|
2
4
  clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
3
5
  warden.winning_strategy.clean_up_csrf?
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Before logout hook to forget the user in the given scope, if it responds
2
4
  # to forget_me! Also clear remember token to ensure the user won't be
3
5
  # remembered again. Notice that we forget the user unless the record is not persisted.
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # After each sign in, if resource responds to failed_attempts, sets it to 0
2
4
  # This is only triggered when the user is explicitly set (with set_user)
3
5
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
4
- if record.respond_to?(:failed_attempts) && warden.authenticated?(options[:scope])
5
- record.update_attribute(:failed_attempts, 0) unless record.failed_attempts.to_i.zero?
6
+ if record.respond_to?(:reset_failed_attempts!) && warden.authenticated?(options[:scope])
7
+ record.reset_failed_attempts!
6
8
  end
7
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Hooks
3
5
  # A small warden proxy so we can remember, forget and
@@ -7,7 +9,7 @@ module Devise
7
9
  include Devise::Controllers::SignInOut
8
10
 
9
11
  attr_reader :warden
10
- delegate :cookies, :env, to: :warden
12
+ delegate :cookies, :request, to: :warden
11
13
 
12
14
  def initialize(warden)
13
15
  @warden = warden
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Warden::Manager.after_set_user except: :fetch do |record, warden, options|
2
4
  scope = options[:scope]
3
5
  if record.respond_to?(:remember_me) && options[:store] != false &&
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Each time a record is set we check whether its session has already timed out
2
4
  # or not, based on last request time. If so, the record is logged out and
3
5
  # redirected to the sign in page. Also, each time the request comes and the
@@ -19,8 +21,8 @@ Warden::Manager.after_set_user do |record, warden, options|
19
21
 
20
22
  proxy = Devise::Hooks::Proxy.new(warden)
21
23
 
22
- if record.timedout?(last_request_at) &&
23
- !env['devise.skip_timeout'] &&
24
+ if !env['devise.skip_timeout'] &&
25
+ record.timedout?(last_request_at) &&
24
26
  !proxy.remember_me_is_active?(record)
25
27
  Devise.sign_out_all_scopes ? proxy.sign_out : proxy.sign_out(scope)
26
28
  throw :warden, scope: scope, message: :timeout
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # After each sign in, update sign in time, sign in count and sign in IP.
2
4
  # This is only triggered when the user is explicitly set (with set_user)
3
5
  # and on authentication. Retrieving the user from session (:fetch) does
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Mailers
3
5
  module Helpers
@@ -5,15 +7,16 @@ module Devise
5
7
 
6
8
  included do
7
9
  include Devise::Controllers::ScopedViews
8
- attr_reader :scope_name, :resource
9
10
  end
10
11
 
11
12
  protected
12
13
 
14
+ attr_reader :scope_name, :resource
15
+
13
16
  # Configure default email options
14
- def devise_mail(record, action, opts={})
17
+ def devise_mail(record, action, opts = {}, &block)
15
18
  initialize_from_record(record)
16
- mail headers_for(action, opts)
19
+ mail headers_for(action, opts), &block
17
20
  end
18
21
 
19
22
  def initialize_from_record(record)
@@ -64,7 +67,7 @@ module Devise
64
67
  template_path
65
68
  end
66
69
 
67
- # Setup a subject doing an I18n lookup. At first, it attempts to set a subject
70
+ # Set up a subject doing an I18n lookup. At first, it attempts to set a subject
68
71
  # based on the current mapping:
69
72
  #
70
73
  # en:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  # Responsible for handling devise mappings and routes configuration. Each
3
5
  # resource configured by devise_for in routes is actually creating a mapping
@@ -44,7 +46,7 @@ module Devise
44
46
  raise "Could not find a valid mapping for #{obj.inspect}"
45
47
  end
46
48
 
47
- def self.find_by_path!(path, path_type=:fullpath)
49
+ def self.find_by_path!(path, path_type = :fullpath)
48
50
  Devise.mappings.each_value { |m| return m if path.include?(m.send(path_type)) }
49
51
  raise "Could not find a valid mapping for path #{path.inspect}"
50
52
  end
data/lib/devise/models.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  class MissingAttribute < StandardError
@@ -12,7 +14,7 @@ module Devise
12
14
 
13
15
  # Creates configuration values for Devise and for the given module.
14
16
  #
15
- # Devise::Models.config(Devise::DatabaseAuthenticatable, :stretches)
17
+ # Devise::Models.config(Devise::Models::DatabaseAuthenticatable, :stretches)
16
18
  #
17
19
  # The line above creates:
18
20
  #
@@ -1,6 +1,8 @@
1
- require 'active_model/version'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'devise/hooks/activatable'
3
4
  require 'devise/hooks/csrf_cleaner'
5
+ require 'devise/rails/deprecated_constant_accessor'
4
6
 
5
7
  module Devise
6
8
  module Models
@@ -54,11 +56,14 @@ module Devise
54
56
  module Authenticatable
55
57
  extend ActiveSupport::Concern
56
58
 
57
- BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
59
+ UNSAFE_ATTRIBUTES_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
58
60
  :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
59
61
  :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
60
62
  :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
61
63
 
64
+ include Devise::DeprecatedConstantAccessor
65
+ deprecate_constant "BLACKLIST_FOR_SERIALIZATION", "Devise::Models::Authenticatable::UNSAFE_ATTRIBUTES_FOR_SERIALIZATION"
66
+
62
67
  included do
63
68
  class_attribute :devise_modules, instance_writer: false
64
69
  self.devise_modules ||= []
@@ -102,18 +107,27 @@ module Devise
102
107
  # and passing a new list of attributes you want to exempt. All attributes
103
108
  # given to :except will simply add names to exempt to Devise internal list.
104
109
  def serializable_hash(options = nil)
105
- options ||= {}
106
- options[:except] = Array(options[:except])
110
+ options = options.try(:dup) || {}
111
+ options[:except] = Array(options[:except]).dup
107
112
 
108
113
  if options[:force_except]
109
114
  options[:except].concat Array(options[:force_except])
110
115
  else
111
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
116
+ options[:except].concat UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
112
117
  end
113
118
 
114
119
  super(options)
115
120
  end
116
121
 
122
+ # Redefine inspect using serializable_hash, to ensure we don't accidentally
123
+ # leak passwords into exceptions.
124
+ def inspect
125
+ inspection = serializable_hash.collect do |k,v|
126
+ "#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
127
+ end
128
+ "#<#{self.class} #{inspection.join(", ")}>"
129
+ end
130
+
117
131
  protected
118
132
 
119
133
  def devise_mailer
@@ -123,16 +137,18 @@ module Devise
123
137
  # This is an internal method called every time Devise needs
124
138
  # to send a notification/mail. This can be overridden if you
125
139
  # need to customize the e-mail delivery logic. For instance,
126
- # if you are using a queue to deliver e-mails (delayed job,
127
- # sidekiq, resque, etc), you must add the delivery to the queue
140
+ # if you are using a queue to deliver e-mails (active job, delayed
141
+ # job, sidekiq, resque, etc), you must add the delivery to the queue
128
142
  # just after the transaction was committed. To achieve this,
129
143
  # you can override send_devise_notification to store the
130
- # deliveries until the after_commit callback is triggered:
144
+ # deliveries until the after_commit callback is triggered.
145
+ #
146
+ # The following example uses Active Job's `deliver_later` :
131
147
  #
132
148
  # class User
133
149
  # devise :database_authenticatable, :confirmable
134
150
  #
135
- # after_commit :send_pending_notifications
151
+ # after_commit :send_pending_devise_notifications
136
152
  #
137
153
  # protected
138
154
  #
@@ -140,27 +156,45 @@ module Devise
140
156
  # # If the record is new or changed then delay the
141
157
  # # delivery until the after_commit callback otherwise
142
158
  # # send now because after_commit will not be called.
143
- # if new_record? || changed?
144
- # pending_notifications << [notification, args]
159
+ # # For Rails < 6 use `changed?` instead of `saved_changes?`.
160
+ # if new_record? || saved_changes?
161
+ # pending_devise_notifications << [notification, args]
145
162
  # else
146
- # devise_mailer.send(notification, self, *args).deliver
163
+ # render_and_send_devise_message(notification, *args)
147
164
  # end
148
165
  # end
149
166
  #
150
- # def send_pending_notifications
151
- # pending_notifications.each do |notification, args|
152
- # devise_mailer.send(notification, self, *args).deliver
167
+ # private
168
+ #
169
+ # def send_pending_devise_notifications
170
+ # pending_devise_notifications.each do |notification, args|
171
+ # render_and_send_devise_message(notification, *args)
153
172
  # end
154
173
  #
155
174
  # # Empty the pending notifications array because the
156
175
  # # after_commit hook can be called multiple times which
157
176
  # # could cause multiple emails to be sent.
158
- # pending_notifications.clear
177
+ # pending_devise_notifications.clear
159
178
  # end
160
179
  #
161
- # def pending_notifications
162
- # @pending_notifications ||= []
180
+ # def pending_devise_notifications
181
+ # @pending_devise_notifications ||= []
163
182
  # end
183
+ #
184
+ # def render_and_send_devise_message(notification, *args)
185
+ # message = devise_mailer.send(notification, self, *args)
186
+ #
187
+ # # Deliver later with Active Job's `deliver_later`
188
+ # if message.respond_to?(:deliver_later)
189
+ # message.deliver_later
190
+ # # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
191
+ # elsif message.respond_to?(:deliver_now)
192
+ # message.deliver_now
193
+ # else
194
+ # message.deliver
195
+ # end
196
+ # end
197
+ #
164
198
  # end
165
199
  #
166
200
  def send_devise_notification(notification, *args)
@@ -235,42 +269,38 @@ module Devise
235
269
  # end
236
270
  #
237
271
  # Finally, notice that Devise also queries for users in other scenarios
238
- # besides authentication, for example when retrieving an user to send
272
+ # besides authentication, for example when retrieving a user to send
239
273
  # an e-mail for password reset. In such cases, find_for_authentication
240
274
  # is not called.
241
275
  def find_for_authentication(tainted_conditions)
242
276
  find_first_by_auth_conditions(tainted_conditions)
243
277
  end
244
278
 
245
- def find_first_by_auth_conditions(tainted_conditions, opts={})
279
+ def find_first_by_auth_conditions(tainted_conditions, opts = {})
246
280
  to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
247
281
  end
248
282
 
249
283
  # Find or initialize a record setting an error if it can't be found.
250
- def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
284
+ def find_or_initialize_with_error_by(attribute, value, error = :invalid) #:nodoc:
251
285
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
252
286
  end
253
287
 
254
288
  # Find or initialize a record with group of attributes based on a list of required attributes.
255
- def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
256
- attributes = attributes.slice(*required_attributes).with_indifferent_access
257
- attributes.delete_if { |key, value| value.blank? }
289
+ def find_or_initialize_with_errors(required_attributes, attributes, error = :invalid) #:nodoc:
290
+ attributes.try(:permit!)
291
+ attributes = attributes.to_h.with_indifferent_access
292
+ .slice(*required_attributes)
293
+ .delete_if { |key, value| value.blank? }
258
294
 
259
295
  if attributes.size == required_attributes.size
260
- record = find_first_by_auth_conditions(attributes)
296
+ record = find_first_by_auth_conditions(attributes) and return record
261
297
  end
262
298
 
263
- unless record
264
- record = new
265
-
299
+ new(devise_parameter_filter.filter(attributes)).tap do |record|
266
300
  required_attributes.each do |key|
267
- value = attributes[key]
268
- record.send("#{key}=", value)
269
- record.errors.add(key, value.present? ? error : :blank)
301
+ record.errors.add(key, attributes[key].blank? ? :blank : error)
270
302
  end
271
303
  end
272
-
273
- record
274
304
  end
275
305
 
276
306
  protected
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  # Confirmable is responsible to verify if an account is already confirmed to
@@ -24,9 +26,11 @@ module Devise
24
26
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
25
27
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
26
28
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
27
- # db field to be setup (t.reconfirmable in migrations). Until confirmed, new email is
29
+ # db field to be set up (t.reconfirmable in migrations). Until confirmed, new email is
28
30
  # stored in unconfirmed email column, and copied to email column on successful
29
- # confirmation.
31
+ # confirmation. Also, when used in conjunction with `send_email_changed_notification`,
32
+ # the notification is sent to the original email when the change is requested,
33
+ # not when the unconfirmed email is confirmed.
30
34
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
31
35
  # You can use this to force the user to confirm within a set period of time.
32
36
  # Confirmable will not generate a new token if a repeat confirmation is requested
@@ -40,17 +44,23 @@ module Devise
40
44
  #
41
45
  module Confirmable
42
46
  extend ActiveSupport::Concern
43
- include ActionView::Helpers::DateHelper
44
47
 
45
48
  included do
46
49
  before_create :generate_confirmation_token, if: :confirmation_required?
47
- after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
50
+ after_create :skip_reconfirmation_in_callback!, if: :send_confirmation_notification?
51
+ if defined?(ActiveRecord) && self < ActiveRecord::Base # ActiveRecord
52
+ after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
53
+ after_commit :send_reconfirmation_instructions, on: :update, if: :reconfirmation_required?
54
+ else # Mongoid
55
+ after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
56
+ after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
57
+ end
48
58
  before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
49
- after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
50
59
  end
51
60
 
52
61
  def initialize(*args, &block)
53
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
54
64
  @reconfirmation_required = false
55
65
  @skip_confirmation_notification = false
56
66
  @raw_confirmation_token = nil
@@ -66,7 +76,7 @@ module Devise
66
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
67
77
  # is already confirmed, add an error to email field. If the user is invalid
68
78
  # add errors
69
- def confirm(args={})
79
+ def confirm(args = {})
70
80
  pending_any_confirmation do
71
81
  if confirmation_period_expired?
72
82
  self.errors.add(:email, :confirmation_period_expired,
@@ -76,7 +86,7 @@ module Devise
76
86
 
77
87
  self.confirmed_at = Time.now.utc
78
88
 
79
- saved = if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
80
90
  skip_reconfirmation!
81
91
  self.email = unconfirmed_email
82
92
  self.unconfirmed_email = nil
@@ -92,11 +102,6 @@ module Devise
92
102
  end
93
103
  end
94
104
 
95
- def confirm!(args={})
96
- ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
97
- confirm(args)
98
- end
99
-
100
105
  # Verifies whether a user is confirmed or not
101
106
  def confirmed?
102
107
  !!confirmed_at
@@ -165,12 +170,17 @@ module Devise
165
170
 
166
171
  protected
167
172
 
173
+ # To not require reconfirmation after creating with #save called in a
174
+ # callback call skip_create_confirmation!
175
+ def skip_reconfirmation_in_callback!
176
+ @skip_reconfirmation_in_callback = true
177
+ end
178
+
168
179
  # A callback method used to deliver confirmation
169
180
  # instructions on creation. This can be overridden
170
181
  # in models to map to a nice sign up e-mail.
171
182
  def send_on_create_confirmation_instructions
172
183
  send_confirmation_instructions
173
- skip_reconfirmation!
174
184
  end
175
185
 
176
186
  # Callback to overwrite if confirmation is required or not.
@@ -181,7 +191,7 @@ module Devise
181
191
  # Checks if the confirmation for the user is within the limit time.
182
192
  # We do this by calculating if the difference between today and the
183
193
  # confirmation sent date does not exceed the confirm in time configured.
184
- # Confirm_within is a model configuration, must always be an integer value.
194
+ # allow_unconfirmed_access_for is a model configuration, must always be an integer value.
185
195
  #
186
196
  # Example:
187
197
  #
@@ -201,7 +211,10 @@ module Devise
201
211
  # confirmation_period_valid? # will always return true
202
212
  #
203
213
  def confirmation_period_valid?
204
- self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
214
+ return true if self.class.allow_unconfirmed_access_for.nil?
215
+ return false if self.class.allow_unconfirmed_access_for == 0.days
216
+
217
+ confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
205
218
  end
206
219
 
207
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -217,7 +230,7 @@ module Devise
217
230
  # confirmation_period_expired? # will always return false
218
231
  #
219
232
  def confirmation_period_expired?
220
- self.class.confirm_within && self.confirmation_sent_at && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
233
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now.utc > self.confirmation_sent_at.utc + self.class.confirm_within)
221
234
  end
222
235
 
223
236
  # Checks whether the record requires any confirmation.
@@ -236,8 +249,7 @@ module Devise
236
249
  if self.confirmation_token && !confirmation_period_expired?
237
250
  @raw_confirmation_token = self.confirmation_token
238
251
  else
239
- raw, _ = Devise.token_generator.generate(self.class, :confirmation_token)
240
- self.confirmation_token = @raw_confirmation_token = raw
252
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
241
253
  self.confirmation_sent_at = Time.now.utc
242
254
  end
243
255
  end
@@ -246,18 +258,44 @@ module Devise
246
258
  generate_confirmation_token && save(validate: false)
247
259
  end
248
260
 
249
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
250
- @reconfirmation_required = true
251
- self.unconfirmed_email = self.email
252
- self.email = self.email_was
253
- self.confirmation_token = nil
254
- generate_confirmation_token
261
+ if Devise.activerecord51?
262
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
263
+ @reconfirmation_required = true
264
+ self.unconfirmed_email = self.email
265
+ self.email = self.email_in_database
266
+ self.confirmation_token = nil
267
+ generate_confirmation_token
268
+ end
269
+ else
270
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
271
+ @reconfirmation_required = true
272
+ self.unconfirmed_email = self.email
273
+ self.email = self.email_was
274
+ self.confirmation_token = nil
275
+ generate_confirmation_token
276
+ end
255
277
  end
256
278
 
257
- def postpone_email_change?
258
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
259
- @bypass_confirmation_postpone = false
260
- postpone
279
+ if Devise.activerecord51?
280
+ def postpone_email_change?
281
+ postpone = self.class.reconfirmable &&
282
+ will_save_change_to_email? &&
283
+ !@bypass_confirmation_postpone &&
284
+ self.email.present? &&
285
+ (!@skip_reconfirmation_in_callback || !self.email_in_database.nil?)
286
+ @bypass_confirmation_postpone = false
287
+ postpone
288
+ end
289
+ else
290
+ def postpone_email_change?
291
+ postpone = self.class.reconfirmable &&
292
+ email_changed? &&
293
+ !@bypass_confirmation_postpone &&
294
+ self.email.present? &&
295
+ (!@skip_reconfirmation_in_callback || !self.email_was.nil?)
296
+ @bypass_confirmation_postpone = false
297
+ postpone
298
+ end
261
299
  end
262
300
 
263
301
  def reconfirmation_required?
@@ -268,6 +306,16 @@ module Devise
268
306
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
269
307
  end
270
308
 
309
+ # With reconfirmable, notify the original email when the user first
310
+ # requests the email change, instead of when the change is confirmed.
311
+ def send_email_changed_notification?
312
+ if self.class.reconfirmable
313
+ self.class.send_email_changed_notification && reconfirmation_required?
314
+ else
315
+ super
316
+ end
317
+ end
318
+
271
319
  # A callback initiated after successfully confirming. This can be
272
320
  # used to insert your own logic that is only run after the user successfully
273
321
  # confirms.
@@ -286,7 +334,7 @@ module Devise
286
334
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
287
335
  # field. If no user is found, returns a new user with an email not found error.
288
336
  # Options must contain the user email
289
- def send_confirmation_instructions(attributes={})
337
+ def send_confirmation_instructions(attributes = {})
290
338
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
291
339
  unless confirmable.try(:persisted?)
292
340
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
@@ -300,7 +348,19 @@ module Devise
300
348
  # If the user is already confirmed, create an error for the user
301
349
  # Options must have the confirmation_token
302
350
  def confirm_by_token(confirmation_token)
351
+ # When the `confirmation_token` parameter is blank, if there are any users with a blank
352
+ # `confirmation_token` in the database, the first one would be confirmed here.
353
+ # The error is being manually added here to ensure no users are confirmed by mistake.
354
+ # This was done in the model for convenience, since validation errors are automatically
355
+ # displayed in the view.
356
+ if confirmation_token.blank?
357
+ confirmable = new
358
+ confirmable.errors.add(:confirmation_token, :blank)
359
+ return confirmable
360
+ end
361
+
303
362
  confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
363
+
304
364
  unless confirmable
305
365
  confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
306
366
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
@@ -316,6 +376,7 @@ module Devise
316
376
 
317
377
  # Find a record for confirmation by unconfirmed email field
318
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
319
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
320
381
  unconfirmed_attributes = attributes.symbolize_keys
321
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)