devise 3.5.1 → 4.7.1

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 (256) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +259 -1076
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +256 -68
  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 +10 -7
  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 +23 -10
  12. data/app/helpers/devise_helper.rb +12 -19
  13. data/app/mailers/devise/mailer.rb +10 -0
  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/mailer/password_change.html.erb +3 -0
  17. data/app/views/devise/passwords/edit.html.erb +5 -5
  18. data/app/views/devise/passwords/new.html.erb +2 -2
  19. data/app/views/devise/registrations/edit.html.erb +9 -5
  20. data/app/views/devise/registrations/new.html.erb +4 -4
  21. data/app/views/devise/sessions/new.html.erb +4 -4
  22. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  23. data/app/views/devise/shared/_links.html.erb +8 -8
  24. data/app/views/devise/unlocks/new.html.erb +2 -2
  25. data/config/locales/en.yml +6 -1
  26. data/lib/devise/controllers/helpers.rb +35 -26
  27. data/lib/devise/controllers/rememberable.rb +11 -2
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +35 -18
  30. data/lib/devise/controllers/store_location.rb +25 -7
  31. data/lib/devise/controllers/url_helpers.rb +2 -0
  32. data/lib/devise/delegator.rb +2 -0
  33. data/lib/devise/encryptor.rb +6 -4
  34. data/lib/devise/failure_app.rb +84 -27
  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 +6 -1
  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 +7 -7
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +7 -4
  44. data/lib/devise/mapping.rb +2 -0
  45. data/lib/devise/models/authenticatable.rb +51 -26
  46. data/lib/devise/models/confirmable.rb +106 -33
  47. data/lib/devise/models/database_authenticatable.rb +97 -21
  48. data/lib/devise/models/lockable.rb +15 -5
  49. data/lib/devise/models/omniauthable.rb +2 -0
  50. data/lib/devise/models/recoverable.rb +32 -24
  51. data/lib/devise/models/registerable.rb +4 -0
  52. data/lib/devise/models/rememberable.rb +42 -26
  53. data/lib/devise/models/timeoutable.rb +2 -6
  54. data/lib/devise/models/trackable.rb +15 -1
  55. data/lib/devise/models/validatable.rb +10 -3
  56. data/lib/devise/models.rb +3 -1
  57. data/lib/devise/modules.rb +2 -0
  58. data/lib/devise/omniauth/config.rb +2 -0
  59. data/lib/devise/omniauth/url_helpers.rb +14 -5
  60. data/lib/devise/omniauth.rb +2 -0
  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/routes.rb +67 -47
  66. data/lib/devise/rails/warden_compat.rb +3 -10
  67. data/lib/devise/rails.rb +7 -16
  68. data/lib/devise/secret_key_finder.rb +27 -0
  69. data/lib/devise/strategies/authenticatable.rb +5 -3
  70. data/lib/devise/strategies/base.rb +2 -0
  71. data/lib/devise/strategies/database_authenticatable.rb +11 -4
  72. data/lib/devise/strategies/rememberable.rb +5 -6
  73. data/lib/devise/test/controller_helpers.rb +165 -0
  74. data/lib/devise/test/integration_helpers.rb +63 -0
  75. data/lib/devise/test_helpers.rb +7 -124
  76. data/lib/devise/time_inflector.rb +2 -0
  77. data/lib/devise/token_generator.rb +3 -41
  78. data/lib/devise/version.rb +3 -1
  79. data/lib/devise.rb +73 -46
  80. data/lib/generators/active_record/devise_generator.rb +29 -10
  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 +4 -2
  85. data/lib/generators/devise/install_generator.rb +17 -0
  86. data/lib/generators/devise/orm_helpers.rb +10 -21
  87. data/lib/generators/devise/views_generator.rb +21 -11
  88. data/lib/generators/mongoid/devise_generator.rb +7 -5
  89. data/lib/generators/templates/README +1 -8
  90. data/lib/generators/templates/controllers/README +1 -1
  91. data/lib/generators/templates/controllers/confirmations_controller.rb +2 -0
  92. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +2 -0
  93. data/lib/generators/templates/controllers/passwords_controller.rb +2 -0
  94. data/lib/generators/templates/controllers/registrations_controller.rb +6 -4
  95. data/lib/generators/templates/controllers/sessions_controller.rb +4 -2
  96. data/lib/generators/templates/controllers/unlocks_controller.rb +2 -0
  97. data/lib/generators/templates/devise.rb +52 -22
  98. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  99. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  100. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  101. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  102. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  103. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  104. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  105. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +4 -1
  106. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -3
  107. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  108. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +7 -2
  109. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  110. metadata +15 -301
  111. data/.gitignore +0 -10
  112. data/.travis.yml +0 -45
  113. data/.yardopts +0 -9
  114. data/CONTRIBUTING.md +0 -14
  115. data/Gemfile +0 -29
  116. data/Gemfile.lock +0 -191
  117. data/Rakefile +0 -36
  118. data/devise.gemspec +0 -29
  119. data/devise.png +0 -0
  120. data/gemfiles/Gemfile.rails-3.2-stable +0 -29
  121. data/gemfiles/Gemfile.rails-3.2-stable.lock +0 -169
  122. data/gemfiles/Gemfile.rails-4.0-stable +0 -29
  123. data/gemfiles/Gemfile.rails-4.0-stable.lock +0 -163
  124. data/gemfiles/Gemfile.rails-4.1-stable +0 -29
  125. data/gemfiles/Gemfile.rails-4.1-stable.lock +0 -169
  126. data/gemfiles/Gemfile.rails-4.2-stable +0 -29
  127. data/gemfiles/Gemfile.rails-4.2-stable.lock +0 -191
  128. data/script/cached-bundle +0 -49
  129. data/script/s3-put +0 -71
  130. data/test/controllers/custom_registrations_controller_test.rb +0 -40
  131. data/test/controllers/custom_strategy_test.rb +0 -62
  132. data/test/controllers/helpers_test.rb +0 -316
  133. data/test/controllers/inherited_controller_i18n_messages_test.rb +0 -51
  134. data/test/controllers/internal_helpers_test.rb +0 -129
  135. data/test/controllers/load_hooks_controller_test.rb +0 -19
  136. data/test/controllers/passwords_controller_test.rb +0 -31
  137. data/test/controllers/sessions_controller_test.rb +0 -103
  138. data/test/controllers/url_helpers_test.rb +0 -65
  139. data/test/delegator_test.rb +0 -19
  140. data/test/devise_test.rb +0 -107
  141. data/test/failure_app_test.rb +0 -298
  142. data/test/generators/active_record_generator_test.rb +0 -109
  143. data/test/generators/controllers_generator_test.rb +0 -48
  144. data/test/generators/devise_generator_test.rb +0 -39
  145. data/test/generators/install_generator_test.rb +0 -13
  146. data/test/generators/mongoid_generator_test.rb +0 -23
  147. data/test/generators/views_generator_test.rb +0 -96
  148. data/test/helpers/devise_helper_test.rb +0 -49
  149. data/test/integration/authenticatable_test.rb +0 -729
  150. data/test/integration/confirmable_test.rb +0 -324
  151. data/test/integration/database_authenticatable_test.rb +0 -95
  152. data/test/integration/http_authenticatable_test.rb +0 -105
  153. data/test/integration/lockable_test.rb +0 -239
  154. data/test/integration/omniauthable_test.rb +0 -133
  155. data/test/integration/recoverable_test.rb +0 -347
  156. data/test/integration/registerable_test.rb +0 -359
  157. data/test/integration/rememberable_test.rb +0 -176
  158. data/test/integration/timeoutable_test.rb +0 -189
  159. data/test/integration/trackable_test.rb +0 -92
  160. data/test/mailers/confirmation_instructions_test.rb +0 -115
  161. data/test/mailers/reset_password_instructions_test.rb +0 -96
  162. data/test/mailers/unlock_instructions_test.rb +0 -91
  163. data/test/mapping_test.rb +0 -134
  164. data/test/models/authenticatable_test.rb +0 -23
  165. data/test/models/confirmable_test.rb +0 -468
  166. data/test/models/database_authenticatable_test.rb +0 -249
  167. data/test/models/lockable_test.rb +0 -328
  168. data/test/models/omniauthable_test.rb +0 -7
  169. data/test/models/recoverable_test.rb +0 -228
  170. data/test/models/registerable_test.rb +0 -7
  171. data/test/models/rememberable_test.rb +0 -204
  172. data/test/models/serializable_test.rb +0 -49
  173. data/test/models/timeoutable_test.rb +0 -51
  174. data/test/models/trackable_test.rb +0 -41
  175. data/test/models/validatable_test.rb +0 -127
  176. data/test/models_test.rb +0 -144
  177. data/test/omniauth/config_test.rb +0 -57
  178. data/test/omniauth/url_helpers_test.rb +0 -54
  179. data/test/orm/active_record.rb +0 -10
  180. data/test/orm/mongoid.rb +0 -13
  181. data/test/parameter_sanitizer_test.rb +0 -81
  182. data/test/rails_app/Rakefile +0 -6
  183. data/test/rails_app/app/active_record/admin.rb +0 -6
  184. data/test/rails_app/app/active_record/shim.rb +0 -2
  185. data/test/rails_app/app/active_record/user.rb +0 -6
  186. data/test/rails_app/app/active_record/user_on_engine.rb +0 -7
  187. data/test/rails_app/app/active_record/user_on_main_app.rb +0 -7
  188. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  189. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  190. data/test/rails_app/app/controllers/application_controller.rb +0 -12
  191. data/test/rails_app/app/controllers/application_with_fake_engine.rb +0 -30
  192. data/test/rails_app/app/controllers/custom/registrations_controller.rb +0 -31
  193. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  194. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  195. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  196. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  197. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  198. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  199. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +0 -3
  200. data/test/rails_app/app/mailers/users/mailer.rb +0 -3
  201. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +0 -4
  202. data/test/rails_app/app/mongoid/admin.rb +0 -29
  203. data/test/rails_app/app/mongoid/shim.rb +0 -23
  204. data/test/rails_app/app/mongoid/user.rb +0 -39
  205. data/test/rails_app/app/mongoid/user_on_engine.rb +0 -39
  206. data/test/rails_app/app/mongoid/user_on_main_app.rb +0 -39
  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/application.rb +0 -40
  223. data/test/rails_app/config/boot.rb +0 -14
  224. data/test/rails_app/config/database.yml +0 -18
  225. data/test/rails_app/config/environment.rb +0 -5
  226. data/test/rails_app/config/environments/development.rb +0 -30
  227. data/test/rails_app/config/environments/production.rb +0 -84
  228. data/test/rails_app/config/environments/test.rb +0 -41
  229. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  230. data/test/rails_app/config/initializers/devise.rb +0 -180
  231. data/test/rails_app/config/initializers/inflections.rb +0 -2
  232. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  233. data/test/rails_app/config/initializers/session_store.rb +0 -1
  234. data/test/rails_app/config/routes.rb +0 -122
  235. data/test/rails_app/config.ru +0 -4
  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_omniauth.rb +0 -13
  241. data/test/rails_app/public/404.html +0 -26
  242. data/test/rails_app/public/422.html +0 -26
  243. data/test/rails_app/public/500.html +0 -26
  244. data/test/rails_app/public/favicon.ico +0 -0
  245. data/test/rails_test.rb +0 -9
  246. data/test/routes_test.rb +0 -264
  247. data/test/support/action_controller/record_identifier.rb +0 -10
  248. data/test/support/assertions.rb +0 -39
  249. data/test/support/helpers.rb +0 -73
  250. data/test/support/integration.rb +0 -92
  251. data/test/support/locale/en.yml +0 -8
  252. data/test/support/mongoid.yml +0 -6
  253. data/test/support/webrat/integrations/rails.rb +0 -24
  254. data/test/test_helper.rb +0 -34
  255. data/test/test_helpers_test.rb +0 -178
  256. data/test/test_models.rb +0 -33
@@ -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
@@ -1,4 +1,5 @@
1
- require 'active_model/version'
1
+ # frozen_string_literal: true
2
+
2
3
  require 'devise/hooks/activatable'
3
4
  require 'devise/hooks/csrf_cleaner'
4
5
 
@@ -102,7 +103,7 @@ module Devise
102
103
  # and passing a new list of attributes you want to exempt. All attributes
103
104
  # given to :except will simply add names to exempt to Devise internal list.
104
105
  def serializable_hash(options = nil)
105
- options ||= {}
106
+ options = options.try(:dup) || {}
106
107
  options[:except] = Array(options[:except])
107
108
 
108
109
  if options[:force_except]
@@ -114,6 +115,15 @@ module Devise
114
115
  super(options)
115
116
  end
116
117
 
118
+ # Redefine inspect using serializable_hash, to ensure we don't accidentally
119
+ # leak passwords into exceptions.
120
+ def inspect
121
+ inspection = serializable_hash.collect do |k,v|
122
+ "#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
123
+ end
124
+ "#<#{self.class} #{inspection.join(", ")}>"
125
+ end
126
+
117
127
  protected
118
128
 
119
129
  def devise_mailer
@@ -123,16 +133,18 @@ module Devise
123
133
  # This is an internal method called every time Devise needs
124
134
  # to send a notification/mail. This can be overridden if you
125
135
  # 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
136
+ # if you are using a queue to deliver e-mails (active job, delayed
137
+ # job, sidekiq, resque, etc), you must add the delivery to the queue
128
138
  # just after the transaction was committed. To achieve this,
129
139
  # you can override send_devise_notification to store the
130
- # deliveries until the after_commit callback is triggered:
140
+ # deliveries until the after_commit callback is triggered.
141
+ #
142
+ # The following example uses Active Job's `deliver_later` :
131
143
  #
132
144
  # class User
133
145
  # devise :database_authenticatable, :confirmable
134
146
  #
135
- # after_commit :send_pending_notifications
147
+ # after_commit :send_pending_devise_notifications
136
148
  #
137
149
  # protected
138
150
  #
@@ -141,26 +153,43 @@ module Devise
141
153
  # # delivery until the after_commit callback otherwise
142
154
  # # send now because after_commit will not be called.
143
155
  # if new_record? || changed?
144
- # pending_notifications << [notification, args]
156
+ # pending_devise_notifications << [notification, args]
145
157
  # else
146
- # devise_mailer.send(notification, self, *args).deliver
158
+ # render_and_send_devise_message(notification, *args)
147
159
  # end
148
160
  # end
149
161
  #
150
- # def send_pending_notifications
151
- # pending_notifications.each do |notification, args|
152
- # devise_mailer.send(notification, self, *args).deliver
162
+ # private
163
+ #
164
+ # def send_pending_devise_notifications
165
+ # pending_devise_notifications.each do |notification, args|
166
+ # render_and_send_devise_message(notification, *args)
153
167
  # end
154
168
  #
155
169
  # # Empty the pending notifications array because the
156
170
  # # after_commit hook can be called multiple times which
157
171
  # # could cause multiple emails to be sent.
158
- # pending_notifications.clear
172
+ # pending_devise_notifications.clear
173
+ # end
174
+ #
175
+ # def pending_devise_notifications
176
+ # @pending_devise_notifications ||= []
159
177
  # end
160
178
  #
161
- # def pending_notifications
162
- # @pending_notifications ||= []
179
+ # def render_and_send_devise_message(notification, *args)
180
+ # message = devise_mailer.send(notification, self, *args)
181
+ #
182
+ # # Deliver later with Active Job's `deliver_later`
183
+ # if message.respond_to?(:deliver_later)
184
+ # message.deliver_later
185
+ # # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
186
+ # elsif message.respond_to?(:deliver_now)
187
+ # message.deliver_now
188
+ # else
189
+ # message.deliver
190
+ # end
163
191
  # end
192
+ #
164
193
  # end
165
194
  #
166
195
  def send_devise_notification(notification, *args)
@@ -235,7 +264,7 @@ module Devise
235
264
  # end
236
265
  #
237
266
  # Finally, notice that Devise also queries for users in other scenarios
238
- # besides authentication, for example when retrieving an user to send
267
+ # besides authentication, for example when retrieving a user to send
239
268
  # an e-mail for password reset. In such cases, find_for_authentication
240
269
  # is not called.
241
270
  def find_for_authentication(tainted_conditions)
@@ -253,24 +282,20 @@ module Devise
253
282
 
254
283
  # Find or initialize a record with group of attributes based on a list of required attributes.
255
284
  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? }
285
+ attributes.try(:permit!)
286
+ attributes = attributes.to_h.with_indifferent_access
287
+ .slice(*required_attributes)
288
+ .delete_if { |key, value| value.blank? }
258
289
 
259
290
  if attributes.size == required_attributes.size
260
- record = find_first_by_auth_conditions(attributes)
291
+ record = find_first_by_auth_conditions(attributes) and return record
261
292
  end
262
293
 
263
- unless record
264
- record = new
265
-
294
+ new(devise_parameter_filter.filter(attributes)).tap do |record|
266
295
  required_attributes.each do |key|
267
- value = attributes[key]
268
- record.send("#{key}=", value)
269
- record.errors.add(key, value.present? ? error : :blank)
296
+ record.errors.add(key, attributes[key].blank? ? :blank : error)
270
297
  end
271
298
  end
272
-
273
- record
274
299
  end
275
300
 
276
301
  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
@@ -7,7 +9,7 @@ module Devise
7
9
  #
8
10
  # Confirmable tracks the following columns:
9
11
  #
10
- # * confirmation_token - An OpenSSL::HMAC.hexdigest of @raw_confirmation_token
12
+ # * confirmation_token - A unique random token
11
13
  # * confirmed_at - A timestamp when the user clicked the confirmation link
12
14
  # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
13
15
  # * unconfirmed_email - An email address copied from the email attr. After confirmation
@@ -24,11 +26,15 @@ 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.
36
+ # Confirmable will not generate a new token if a repeat confirmation is requested
37
+ # during this time frame, unless the user's email changed too.
32
38
  #
33
39
  # == Examples
34
40
  #
@@ -38,17 +44,23 @@ module Devise
38
44
  #
39
45
  module Confirmable
40
46
  extend ActiveSupport::Concern
41
- include ActionView::Helpers::DateHelper
42
47
 
43
48
  included do
44
49
  before_create :generate_confirmation_token, if: :confirmation_required?
45
- 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
46
58
  before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
47
- after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
48
59
  end
49
60
 
50
61
  def initialize(*args, &block)
51
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
52
64
  @reconfirmation_required = false
53
65
  @skip_confirmation_notification = false
54
66
  @raw_confirmation_token = nil
@@ -74,7 +86,7 @@ module Devise
74
86
 
75
87
  self.confirmed_at = Time.now.utc
76
88
 
77
- saved = if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
78
90
  skip_reconfirmation!
79
91
  self.email = unconfirmed_email
80
92
  self.unconfirmed_email = nil
@@ -90,11 +102,6 @@ module Devise
90
102
  end
91
103
  end
92
104
 
93
- def confirm!(args={})
94
- ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
95
- confirm(args)
96
- end
97
-
98
105
  # Verifies whether a user is confirmed or not
99
106
  def confirmed?
100
107
  !!confirmed_at
@@ -163,6 +170,12 @@ module Devise
163
170
 
164
171
  protected
165
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
+
166
179
  # A callback method used to deliver confirmation
167
180
  # instructions on creation. This can be overridden
168
181
  # in models to map to a nice sign up e-mail.
@@ -178,7 +191,7 @@ module Devise
178
191
  # Checks if the confirmation for the user is within the limit time.
179
192
  # We do this by calculating if the difference between today and the
180
193
  # confirmation sent date does not exceed the confirm in time configured.
181
- # 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.
182
195
  #
183
196
  # Example:
184
197
  #
@@ -198,7 +211,10 @@ module Devise
198
211
  # confirmation_period_valid? # will always return true
199
212
  #
200
213
  def confirmation_period_valid?
201
- 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
202
218
  end
203
219
 
204
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -214,7 +230,7 @@ module Devise
214
230
  # confirmation_period_expired? # will always return false
215
231
  #
216
232
  def confirmation_period_expired?
217
- self.class.confirm_within && (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)
218
234
  end
219
235
 
220
236
  # Checks whether the record requires any confirmation.
@@ -230,37 +246,76 @@ module Devise
230
246
  # Generates a new random token for confirmation, and stores
231
247
  # the time this token is being generated in confirmation_sent_at
232
248
  def generate_confirmation_token
233
- raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
234
- @raw_confirmation_token = raw
235
- self.confirmation_token = enc
236
- self.confirmation_sent_at = Time.now.utc
249
+ if self.confirmation_token && !confirmation_period_expired?
250
+ @raw_confirmation_token = self.confirmation_token
251
+ else
252
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
253
+ self.confirmation_sent_at = Time.now.utc
254
+ end
237
255
  end
238
256
 
239
257
  def generate_confirmation_token!
240
258
  generate_confirmation_token && save(validate: false)
241
259
  end
242
260
 
243
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
244
- @reconfirmation_required = true
245
- self.unconfirmed_email = self.email
246
- self.email = self.email_was
247
- 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
248
277
  end
249
278
 
250
- def postpone_email_change?
251
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
252
- @bypass_confirmation_postpone = false
253
- 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
254
299
  end
255
300
 
256
301
  def reconfirmation_required?
257
- self.class.reconfirmable && @reconfirmation_required && self.email.present?
302
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
258
303
  end
259
304
 
260
305
  def send_confirmation_notification?
261
306
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
262
307
  end
263
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
+
264
319
  # A callback initiated after successfully confirming. This can be
265
320
  # used to insert your own logic that is only run after the user successfully
266
321
  # confirms.
@@ -293,17 +348,35 @@ module Devise
293
348
  # If the user is already confirmed, create an error for the user
294
349
  # Options must have the confirmation_token
295
350
  def confirm_by_token(confirmation_token)
296
- original_token = confirmation_token
297
- confirmation_token = Devise.token_generator.digest(self, :confirmation_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
+
362
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
363
+
364
+ unless confirmable
365
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
366
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
367
+ end
368
+
369
+ # TODO: replace above lines with
370
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
371
+ # after enough time has passed that Devise clients do not use digested tokens
298
372
 
299
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
300
373
  confirmable.confirm if confirmable.persisted?
301
- confirmable.confirmation_token = original_token
302
374
  confirmable
303
375
  end
304
376
 
305
377
  # Find a record for confirmation by unconfirmed email field
306
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
307
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
308
381
  unconfirmed_attributes = attributes.symbolize_keys
309
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)
@@ -1,25 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
- require 'devise/encryptor'
3
4
 
4
5
  module Devise
5
- def self.bcrypt(klass, password)
6
- ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
7
- Devise::Encryptor.digest(klass, password)
8
- end
9
-
10
6
  module Models
11
- # Authenticatable Module, responsible for encrypting password and validating
12
- # authenticity of a user while signing in.
7
+ # Authenticatable Module, responsible for hashing the password and
8
+ # validating the authenticity of a user while signing in.
13
9
  #
14
10
  # == Options
15
11
  #
16
- # DatabaseAuthenticable adds the following options to devise_for:
12
+ # DatabaseAuthenticatable adds the following options to devise_for:
17
13
  #
18
14
  # * +pepper+: a random string used to provide a more secure hash. Use
19
- # `rake secret` to generate new keys.
15
+ # `rails secret` to generate new keys.
20
16
  #
21
17
  # * +stretches+: the cost given to bcrypt.
22
18
  #
19
+ # * +send_email_changed_notification+: notify original email when it changes.
20
+ #
21
+ # * +send_password_change_notification+: notify email when password changes.
22
+ #
23
23
  # == Examples
24
24
  #
25
25
  # User.find(1).valid_password?('password123') # returns true/false
@@ -28,15 +28,36 @@ module Devise
28
28
  extend ActiveSupport::Concern
29
29
 
30
30
  included do
31
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
32
+ after_update :send_password_change_notification, if: :send_password_change_notification?
33
+
31
34
  attr_reader :password, :current_password
32
35
  attr_accessor :password_confirmation
33
36
  end
34
37
 
38
+ def initialize(*args, &block)
39
+ @skip_email_changed_notification = false
40
+ @skip_password_change_notification = false
41
+ super
42
+ end
43
+
44
+ # Skips sending the email changed notification after_update
45
+ def skip_email_changed_notification!
46
+ @skip_email_changed_notification = true
47
+ end
48
+
49
+ # Skips sending the password change notification after_update
50
+ def skip_password_change_notification!
51
+ @skip_password_change_notification = true
52
+ end
53
+
35
54
  def self.required_fields(klass)
36
55
  [:encrypted_password] + klass.authentication_keys
37
56
  end
38
57
 
39
- # Generates password encryption based on the given value.
58
+ # Generates a hashed password based on the given value.
59
+ # For legacy reasons, we use `encrypted_password` to store
60
+ # the hashed password.
40
61
  def password=(new_password)
41
62
  @password = new_password
42
63
  self.encrypted_password = password_digest(@password) if @password.present?
@@ -60,6 +81,15 @@ module Devise
60
81
  # their password). In case the password field is rejected, the confirmation
61
82
  # is also rejected as long as it is also blank.
62
83
  def update_with_password(params, *options)
84
+ if options.present?
85
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
86
+ [Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
87
+ (`options`) is deprecated and it will be removed in the next major version.
88
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
89
+ from your code.
90
+ DEPRECATION
91
+ end
92
+
63
93
  current_password = params.delete(:current_password)
64
94
 
65
95
  if params[:password].blank?
@@ -68,11 +98,11 @@ module Devise
68
98
  end
69
99
 
70
100
  result = if valid_password?(current_password)
71
- update_attributes(params, *options)
101
+ update(params, *options)
72
102
  else
73
- self.assign_attributes(params, *options)
74
- self.valid?
75
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
103
+ assign_attributes(params, *options)
104
+ valid?
105
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
76
106
  false
77
107
  end
78
108
 
@@ -93,10 +123,19 @@ module Devise
93
123
  # end
94
124
  #
95
125
  def update_without_password(params, *options)
126
+ if options.present?
127
+ ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
128
+ [Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
129
+ (`options`) is deprecated and it will be removed in the next major version.
130
+ It was added to support a feature deprecated in Rails 4, so you can safely remove it
131
+ from your code.
132
+ DEPRECATION
133
+ end
134
+
96
135
  params.delete(:password)
97
136
  params.delete(:password_confirmation)
98
137
 
99
- result = update_attributes(params, *options)
138
+ result = update(params, *options)
100
139
  clean_up_passwords
101
140
  result
102
141
  end
@@ -108,8 +147,8 @@ module Devise
108
147
  result = if valid_password?(current_password)
109
148
  destroy
110
149
  else
111
- self.valid?
112
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
150
+ valid?
151
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
113
152
  false
114
153
  end
115
154
 
@@ -134,19 +173,56 @@ module Devise
134
173
  encrypted_password[0,29] if encrypted_password
135
174
  end
136
175
 
176
+ if Devise.activerecord51?
177
+ # Send notification to user when email changes.
178
+ def send_email_changed_notification
179
+ send_devise_notification(:email_changed, to: email_before_last_save)
180
+ end
181
+ else
182
+ # Send notification to user when email changes.
183
+ def send_email_changed_notification
184
+ send_devise_notification(:email_changed, to: email_was)
185
+ end
186
+ end
187
+
188
+ # Send notification to user when password changes.
189
+ def send_password_change_notification
190
+ send_devise_notification(:password_change)
191
+ end
192
+
137
193
  protected
138
194
 
139
- # Digests the password using bcrypt. Custom encryption should override
195
+ # Hashes the password using bcrypt. Custom hash functions should override
140
196
  # this method to apply their own algorithm.
141
197
  #
142
198
  # See https://github.com/plataformatec/devise-encryptable for examples
143
- # of other encryption engines.
199
+ # of other hashing engines.
144
200
  def password_digest(password)
145
201
  Devise::Encryptor.digest(self.class, password)
146
202
  end
147
203
 
204
+ if Devise.activerecord51?
205
+ def send_email_changed_notification?
206
+ self.class.send_email_changed_notification && saved_change_to_email? && !@skip_email_changed_notification
207
+ end
208
+ else
209
+ def send_email_changed_notification?
210
+ self.class.send_email_changed_notification && email_changed? && !@skip_email_changed_notification
211
+ end
212
+ end
213
+
214
+ if Devise.activerecord51?
215
+ def send_password_change_notification?
216
+ self.class.send_password_change_notification && saved_change_to_encrypted_password? && !@skip_password_change_notification
217
+ end
218
+ else
219
+ def send_password_change_notification?
220
+ self.class.send_password_change_notification && encrypted_password_changed? && !@skip_password_change_notification
221
+ end
222
+ end
223
+
148
224
  module ClassMethods
149
- Devise::Models.config(self, :pepper, :stretches)
225
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
150
226
 
151
227
  # We assume this method already gets the sanitized values from the
152
228
  # DatabaseAuthenticatable strategy. If you are using this method on