devise 3.2.2 → 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 (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +281 -957
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +410 -120
  5. data/app/controllers/devise/confirmations_controller.rb +11 -5
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
  7. data/app/controllers/devise/passwords_controller.rb +21 -8
  8. data/app/controllers/devise/registrations_controller.rb +59 -26
  9. data/app/controllers/devise/sessions_controller.rb +47 -17
  10. data/app/controllers/devise/unlocks_controller.rb +9 -4
  11. data/app/controllers/devise_controller.rb +69 -33
  12. data/app/helpers/devise_helper.rb +23 -18
  13. data/app/mailers/devise/mailer.rb +13 -3
  14. data/app/views/devise/confirmations/new.html.erb +9 -5
  15. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  16. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  17. data/app/views/devise/mailer/password_change.html.erb +3 -0
  18. data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
  19. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  20. data/app/views/devise/passwords/edit.html.erb +16 -7
  21. data/app/views/devise/passwords/new.html.erb +9 -5
  22. data/app/views/devise/registrations/edit.html.erb +29 -15
  23. data/app/views/devise/registrations/new.html.erb +20 -9
  24. data/app/views/devise/sessions/new.html.erb +19 -10
  25. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  26. data/app/views/devise/shared/{_links.erb → _links.html.erb} +9 -9
  27. data/app/views/devise/unlocks/new.html.erb +9 -5
  28. data/config/locales/en.yml +24 -18
  29. data/lib/devise/controllers/helpers.rb +113 -33
  30. data/lib/devise/controllers/rememberable.rb +15 -6
  31. data/lib/devise/controllers/scoped_views.rb +3 -1
  32. data/lib/devise/controllers/sign_in_out.rb +47 -29
  33. data/lib/devise/controllers/store_location.rb +31 -5
  34. data/lib/devise/controllers/url_helpers.rb +10 -8
  35. data/lib/devise/delegator.rb +2 -0
  36. data/lib/devise/encryptor.rb +24 -0
  37. data/lib/devise/failure_app.rb +119 -40
  38. data/lib/devise/hooks/activatable.rb +7 -6
  39. data/lib/devise/hooks/csrf_cleaner.rb +5 -1
  40. data/lib/devise/hooks/forgetable.rb +2 -0
  41. data/lib/devise/hooks/lockable.rb +5 -3
  42. data/lib/devise/hooks/proxy.rb +4 -2
  43. data/lib/devise/hooks/rememberable.rb +4 -2
  44. data/lib/devise/hooks/timeoutable.rb +16 -9
  45. data/lib/devise/hooks/trackable.rb +3 -1
  46. data/lib/devise/mailers/helpers.rb +15 -12
  47. data/lib/devise/mapping.rb +9 -3
  48. data/lib/devise/models/authenticatable.rb +91 -61
  49. data/lib/devise/models/confirmable.rb +138 -43
  50. data/lib/devise/models/database_authenticatable.rb +112 -31
  51. data/lib/devise/models/lockable.rb +39 -18
  52. data/lib/devise/models/omniauthable.rb +3 -1
  53. data/lib/devise/models/recoverable.rb +64 -28
  54. data/lib/devise/models/registerable.rb +4 -0
  55. data/lib/devise/models/rememberable.rb +62 -33
  56. data/lib/devise/models/timeoutable.rb +4 -8
  57. data/lib/devise/models/trackable.rb +20 -4
  58. data/lib/devise/models/validatable.rb +16 -9
  59. data/lib/devise/models.rb +3 -1
  60. data/lib/devise/modules.rb +12 -10
  61. data/lib/devise/omniauth/config.rb +2 -0
  62. data/lib/devise/omniauth/url_helpers.rb +14 -5
  63. data/lib/devise/omniauth.rb +4 -5
  64. data/lib/devise/orm/active_record.rb +5 -1
  65. data/lib/devise/orm/mongoid.rb +6 -2
  66. data/lib/devise/parameter_filter.rb +4 -0
  67. data/lib/devise/parameter_sanitizer.rb +139 -65
  68. data/lib/devise/rails/deprecated_constant_accessor.rb +39 -0
  69. data/lib/devise/rails/routes.rb +151 -120
  70. data/lib/devise/rails/warden_compat.rb +3 -10
  71. data/lib/devise/rails.rb +10 -13
  72. data/lib/devise/secret_key_finder.rb +27 -0
  73. data/lib/devise/strategies/authenticatable.rb +21 -10
  74. data/lib/devise/strategies/base.rb +3 -1
  75. data/lib/devise/strategies/database_authenticatable.rb +14 -6
  76. data/lib/devise/strategies/rememberable.rb +15 -3
  77. data/lib/devise/test/controller_helpers.rb +167 -0
  78. data/lib/devise/test/integration_helpers.rb +63 -0
  79. data/lib/devise/test_helpers.rb +7 -124
  80. data/lib/devise/time_inflector.rb +4 -2
  81. data/lib/devise/token_generator.rb +3 -41
  82. data/lib/devise/version.rb +3 -1
  83. data/lib/devise.rb +107 -84
  84. data/lib/generators/active_record/devise_generator.rb +64 -12
  85. data/lib/generators/active_record/templates/migration.rb +9 -7
  86. data/lib/generators/active_record/templates/migration_existing.rb +9 -7
  87. data/lib/generators/devise/controllers_generator.rb +46 -0
  88. data/lib/generators/devise/devise_generator.rb +8 -6
  89. data/lib/generators/devise/install_generator.rb +18 -1
  90. data/lib/generators/devise/orm_helpers.rb +10 -21
  91. data/lib/generators/devise/views_generator.rb +49 -28
  92. data/lib/generators/mongoid/devise_generator.rb +21 -19
  93. data/lib/generators/templates/README +13 -12
  94. data/lib/generators/templates/controllers/README +14 -0
  95. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  96. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  97. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  98. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  99. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  100. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  101. data/lib/generators/templates/devise.rb +94 -37
  102. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  103. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  104. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  105. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  106. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  107. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +6 -2
  108. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +12 -4
  109. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +5 -2
  110. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +14 -6
  111. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +12 -4
  112. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +11 -6
  113. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +5 -2
  114. metadata +50 -284
  115. data/.gitignore +0 -10
  116. data/.travis.yml +0 -20
  117. data/.yardopts +0 -9
  118. data/CONTRIBUTING.md +0 -14
  119. data/Gemfile +0 -31
  120. data/Gemfile.lock +0 -160
  121. data/Rakefile +0 -35
  122. data/devise.gemspec +0 -27
  123. data/devise.png +0 -0
  124. data/gemfiles/Gemfile.rails-3.2.x +0 -31
  125. data/gemfiles/Gemfile.rails-3.2.x.lock +0 -159
  126. data/test/controllers/custom_strategy_test.rb +0 -62
  127. data/test/controllers/helpers_test.rb +0 -276
  128. data/test/controllers/internal_helpers_test.rb +0 -120
  129. data/test/controllers/passwords_controller_test.rb +0 -31
  130. data/test/controllers/sessions_controller_test.rb +0 -99
  131. data/test/controllers/url_helpers_test.rb +0 -59
  132. data/test/delegator_test.rb +0 -19
  133. data/test/devise_test.rb +0 -94
  134. data/test/failure_app_test.rb +0 -232
  135. data/test/generators/active_record_generator_test.rb +0 -103
  136. data/test/generators/devise_generator_test.rb +0 -39
  137. data/test/generators/install_generator_test.rb +0 -13
  138. data/test/generators/mongoid_generator_test.rb +0 -23
  139. data/test/generators/views_generator_test.rb +0 -67
  140. data/test/helpers/devise_helper_test.rb +0 -51
  141. data/test/integration/authenticatable_test.rb +0 -713
  142. data/test/integration/confirmable_test.rb +0 -284
  143. data/test/integration/database_authenticatable_test.rb +0 -84
  144. data/test/integration/http_authenticatable_test.rb +0 -105
  145. data/test/integration/lockable_test.rb +0 -239
  146. data/test/integration/omniauthable_test.rb +0 -133
  147. data/test/integration/recoverable_test.rb +0 -334
  148. data/test/integration/registerable_test.rb +0 -349
  149. data/test/integration/rememberable_test.rb +0 -167
  150. data/test/integration/timeoutable_test.rb +0 -183
  151. data/test/integration/trackable_test.rb +0 -92
  152. data/test/mailers/confirmation_instructions_test.rb +0 -115
  153. data/test/mailers/reset_password_instructions_test.rb +0 -96
  154. data/test/mailers/unlock_instructions_test.rb +0 -91
  155. data/test/mapping_test.rb +0 -127
  156. data/test/models/authenticatable_test.rb +0 -13
  157. data/test/models/confirmable_test.rb +0 -454
  158. data/test/models/database_authenticatable_test.rb +0 -249
  159. data/test/models/lockable_test.rb +0 -298
  160. data/test/models/omniauthable_test.rb +0 -7
  161. data/test/models/recoverable_test.rb +0 -184
  162. data/test/models/registerable_test.rb +0 -7
  163. data/test/models/rememberable_test.rb +0 -183
  164. data/test/models/serializable_test.rb +0 -49
  165. data/test/models/timeoutable_test.rb +0 -51
  166. data/test/models/trackable_test.rb +0 -13
  167. data/test/models/validatable_test.rb +0 -127
  168. data/test/models_test.rb +0 -144
  169. data/test/omniauth/config_test.rb +0 -57
  170. data/test/omniauth/url_helpers_test.rb +0 -54
  171. data/test/orm/active_record.rb +0 -10
  172. data/test/orm/mongoid.rb +0 -13
  173. data/test/parameter_sanitizer_test.rb +0 -81
  174. data/test/rails_app/Rakefile +0 -6
  175. data/test/rails_app/app/active_record/admin.rb +0 -6
  176. data/test/rails_app/app/active_record/shim.rb +0 -2
  177. data/test/rails_app/app/active_record/user.rb +0 -6
  178. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  179. data/test/rails_app/app/controllers/admins_controller.rb +0 -11
  180. data/test/rails_app/app/controllers/application_controller.rb +0 -9
  181. data/test/rails_app/app/controllers/home_controller.rb +0 -25
  182. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  183. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  184. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  185. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  186. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  187. data/test/rails_app/app/mailers/users/mailer.rb +0 -12
  188. data/test/rails_app/app/mongoid/admin.rb +0 -29
  189. data/test/rails_app/app/mongoid/shim.rb +0 -23
  190. data/test/rails_app/app/mongoid/user.rb +0 -39
  191. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  192. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  193. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  194. data/test/rails_app/app/views/home/index.html.erb +0 -1
  195. data/test/rails_app/app/views/home/join.html.erb +0 -1
  196. data/test/rails_app/app/views/home/private.html.erb +0 -1
  197. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  198. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  199. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  200. data/test/rails_app/app/views/users/index.html.erb +0 -1
  201. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  202. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  203. data/test/rails_app/bin/bundle +0 -3
  204. data/test/rails_app/bin/rails +0 -4
  205. data/test/rails_app/bin/rake +0 -4
  206. data/test/rails_app/config/application.rb +0 -40
  207. data/test/rails_app/config/boot.rb +0 -14
  208. data/test/rails_app/config/database.yml +0 -18
  209. data/test/rails_app/config/environment.rb +0 -5
  210. data/test/rails_app/config/environments/development.rb +0 -30
  211. data/test/rails_app/config/environments/production.rb +0 -80
  212. data/test/rails_app/config/environments/test.rb +0 -36
  213. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  214. data/test/rails_app/config/initializers/devise.rb +0 -181
  215. data/test/rails_app/config/initializers/inflections.rb +0 -2
  216. data/test/rails_app/config/initializers/secret_token.rb +0 -8
  217. data/test/rails_app/config/initializers/session_store.rb +0 -1
  218. data/test/rails_app/config/routes.rb +0 -104
  219. data/test/rails_app/config.ru +0 -4
  220. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  221. data/test/rails_app/db/schema.rb +0 -55
  222. data/test/rails_app/lib/shared_admin.rb +0 -17
  223. data/test/rails_app/lib/shared_user.rb +0 -29
  224. data/test/rails_app/public/404.html +0 -26
  225. data/test/rails_app/public/422.html +0 -26
  226. data/test/rails_app/public/500.html +0 -26
  227. data/test/rails_app/public/favicon.ico +0 -0
  228. data/test/routes_test.rb +0 -250
  229. data/test/support/assertions.rb +0 -40
  230. data/test/support/helpers.rb +0 -70
  231. data/test/support/integration.rb +0 -92
  232. data/test/support/locale/en.yml +0 -8
  233. data/test/support/webrat/integrations/rails.rb +0 -24
  234. data/test/test_helper.rb +0 -27
  235. data/test/test_helpers_test.rb +0 -173
  236. data/test/test_models.rb +0 -33
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/hooks/activatable'
2
4
  require 'devise/hooks/csrf_cleaner'
5
+ require 'devise/rails/deprecated_constant_accessor'
3
6
 
4
7
  module Devise
5
8
  module Models
@@ -29,7 +32,7 @@ module Devise
29
32
  # It also accepts an array specifying the strategies that should allow params authentication.
30
33
  #
31
34
  # * +skip_session_storage+: By default Devise will store the user in session.
32
- # By default is set to :skip_session_storage => [:http_auth].
35
+ # By default is set to skip_session_storage: [:http_auth].
33
36
  #
34
37
  # == active_for_authentication?
35
38
  #
@@ -37,7 +40,7 @@ module Devise
37
40
  # calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
38
41
  # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
39
42
  #
40
- # You overwrite this method yourself, but if you do, don't forget to call super:
43
+ # You can overwrite this method yourself, but if you do, don't forget to call super:
41
44
  #
42
45
  # def active_for_authentication?
43
46
  # super && special_condition_is_valid?
@@ -53,13 +56,16 @@ module Devise
53
56
  module Authenticatable
54
57
  extend ActiveSupport::Concern
55
58
 
56
- 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,
57
60
  :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
58
61
  :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
59
- :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token]
62
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
63
+
64
+ include Devise::DeprecatedConstantAccessor
65
+ deprecate_constant "BLACKLIST_FOR_SERIALIZATION", "Devise::Models::Authenticatable::UNSAFE_ATTRIBUTES_FOR_SERIALIZATION"
60
66
 
61
67
  included do
62
- class_attribute :devise_modules, :instance_writer => false
68
+ class_attribute :devise_modules, instance_writer: false
63
69
  self.devise_modules ||= []
64
70
 
65
71
  before_validation :downcase_keys
@@ -95,29 +101,31 @@ module Devise
95
101
  def authenticatable_salt
96
102
  end
97
103
 
98
- array = %w(serializable_hash)
99
- # to_xml does not call serializable_hash on 3.1
100
- array << "to_xml" if Rails::VERSION::STRING[0,3] == "3.1"
101
-
102
- array.each do |method|
103
- class_eval <<-RUBY, __FILE__, __LINE__
104
- # Redefine to_xml and serializable_hash in models for more secure defaults.
105
- # By default, it removes from the serializable model all attributes that
106
- # are *not* accessible. You can remove this default by using :force_except
107
- # and passing a new list of attributes you want to exempt. All attributes
108
- # given to :except will simply add names to exempt to Devise internal list.
109
- def #{method}(options=nil)
110
- options ||= {}
111
- options[:except] = Array(options[:except])
112
-
113
- if options[:force_except]
114
- options[:except].concat Array(options[:force_except])
115
- else
116
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
117
- end
118
- super(options)
119
- end
120
- RUBY
104
+ # Redefine serializable_hash in models for more secure defaults.
105
+ # By default, it removes from the serializable model all attributes that
106
+ # are *not* accessible. You can remove this default by using :force_except
107
+ # and passing a new list of attributes you want to exempt. All attributes
108
+ # given to :except will simply add names to exempt to Devise internal list.
109
+ def serializable_hash(options = nil)
110
+ options = options.try(:dup) || {}
111
+ options[:except] = Array(options[:except]).dup
112
+
113
+ if options[:force_except]
114
+ options[:except].concat Array(options[:force_except])
115
+ else
116
+ options[:except].concat UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
117
+ end
118
+
119
+ super(options)
120
+ end
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(", ")}>"
121
129
  end
122
130
 
123
131
  protected
@@ -127,18 +135,20 @@ module Devise
127
135
  end
128
136
 
129
137
  # This is an internal method called every time Devise needs
130
- # to send a notification/mail. This can be overriden if you
138
+ # to send a notification/mail. This can be overridden if you
131
139
  # need to customize the e-mail delivery logic. For instance,
132
- # if you are using a queue to deliver e-mails (delayed job,
133
- # 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
134
142
  # just after the transaction was committed. To achieve this,
135
143
  # you can override send_devise_notification to store the
136
- # 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` :
137
147
  #
138
148
  # class User
139
149
  # devise :database_authenticatable, :confirmable
140
150
  #
141
- # after_commit :send_pending_notifications
151
+ # after_commit :send_pending_devise_notifications
142
152
  #
143
153
  # protected
144
154
  #
@@ -146,31 +156,55 @@ module Devise
146
156
  # # If the record is new or changed then delay the
147
157
  # # delivery until the after_commit callback otherwise
148
158
  # # send now because after_commit will not be called.
149
- # if new_record? || changed?
150
- # 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]
151
162
  # else
152
- # devise_mailer.send(notification, self, *args).deliver
163
+ # render_and_send_devise_message(notification, *args)
153
164
  # end
154
165
  # end
155
166
  #
156
- # def send_pending_notifications
157
- # pending_notifications.each do |notification, args|
158
- # 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)
159
172
  # end
160
173
  #
161
174
  # # Empty the pending notifications array because the
162
175
  # # after_commit hook can be called multiple times which
163
176
  # # could cause multiple emails to be sent.
164
- # pending_notifications.clear
177
+ # pending_devise_notifications.clear
178
+ # end
179
+ #
180
+ # def pending_devise_notifications
181
+ # @pending_devise_notifications ||= []
165
182
  # end
166
183
  #
167
- # def pending_notifications
168
- # @pending_notifications ||= []
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
169
196
  # end
197
+ #
170
198
  # end
171
199
  #
172
200
  def send_devise_notification(notification, *args)
173
- devise_mailer.send(notification, self, *args).deliver
201
+ message = devise_mailer.send(notification, self, *args)
202
+ # Remove once we move to Rails 4.2+ only.
203
+ if message.respond_to?(:deliver_now)
204
+ message.deliver_now
205
+ else
206
+ message.deliver
207
+ end
174
208
  end
175
209
 
176
210
  def downcase_keys
@@ -231,46 +265,42 @@ module Devise
231
265
  # Example:
232
266
  #
233
267
  # def self.find_for_authentication(tainted_conditions)
234
- # find_first_by_auth_conditions(tainted_conditions, :active => true)
268
+ # find_first_by_auth_conditions(tainted_conditions, active: true)
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
- # Find an 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:
283
+ # Find or initialize a record setting an error if it can't be found.
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
- # Find an initialize a 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)
257
- attributes.delete_if { |key, value| value.blank? }
288
+ # Find or initialize a record with group of attributes based on a list of required attributes.
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
@@ -5,42 +7,60 @@ module Devise
5
7
  # Confirmation instructions are sent to the user email after creating a
6
8
  # record and when manually requested by a new confirmation instruction request.
7
9
  #
10
+ # Confirmable tracks the following columns:
11
+ #
12
+ # * confirmation_token - A unique random token
13
+ # * confirmed_at - A timestamp when the user clicked the confirmation link
14
+ # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
15
+ # * unconfirmed_email - An email address copied from the email attr. After confirmation
16
+ # this value is copied to the email attr then cleared
17
+ #
8
18
  # == Options
9
19
  #
10
20
  # Confirmable adds the following options to +devise+:
11
21
  #
12
- # * +allow_unconfirmed_access_for+: the time you want to allow the user to access his account
22
+ # * +allow_unconfirmed_access_for+: the time you want to allow the user to access their account
13
23
  # before confirming it. After this period, the user access is denied. You can
14
24
  # use this to let your user access some features of your application without
15
25
  # confirming the account, but blocking it after a certain period (ie 7 days).
16
26
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
17
27
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
28
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
19
- # 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
20
30
  # stored in unconfirmed email column, and copied to email column on successful
21
- # 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.
22
34
  # * +confirm_within+: the time before a sent confirmation token becomes invalid.
23
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.
24
38
  #
25
39
  # == Examples
26
40
  #
27
- # User.find(1).confirm! # returns true unless it's already confirmed
41
+ # User.find(1).confirm # returns true unless it's already confirmed
28
42
  # User.find(1).confirmed? # true/false
29
43
  # User.find(1).send_confirmation_instructions # manually send instructions
30
44
  #
31
45
  module Confirmable
32
46
  extend ActiveSupport::Concern
33
- include ActionView::Helpers::DateHelper
34
47
 
35
48
  included do
36
- before_create :generate_confirmation_token, :if => :confirmation_required?
37
- after_create :send_on_create_confirmation_instructions, :if => :send_confirmation_notification?
38
- before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, :if => :postpone_email_change?
39
- after_update :send_reconfirmation_instructions, :if => :reconfirmation_required?
49
+ before_create :generate_confirmation_token, if: :confirmation_required?
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
58
+ before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
40
59
  end
41
60
 
42
61
  def initialize(*args, &block)
43
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
44
64
  @reconfirmation_required = false
45
65
  @skip_confirmation_notification = false
46
66
  @raw_confirmation_token = nil
@@ -56,26 +76,25 @@ module Devise
56
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
57
77
  # is already confirmed, add an error to email field. If the user is invalid
58
78
  # add errors
59
- def confirm!
79
+ def confirm(args = {})
60
80
  pending_any_confirmation do
61
81
  if confirmation_period_expired?
62
82
  self.errors.add(:email, :confirmation_period_expired,
63
- :period => Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
83
+ period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
64
84
  return false
65
85
  end
66
86
 
67
- self.confirmation_token = nil
68
87
  self.confirmed_at = Time.now.utc
69
88
 
70
- saved = if self.class.reconfirmable && unconfirmed_email.present?
89
+ saved = if pending_reconfirmation?
71
90
  skip_reconfirmation!
72
91
  self.email = unconfirmed_email
73
92
  self.unconfirmed_email = nil
74
93
 
75
94
  # We need to validate in such cases to enforce e-mail uniqueness
76
- save(:validate => true)
95
+ save(validate: true)
77
96
  else
78
- save(:validate => false)
97
+ save(validate: args[:ensure_valid] == true)
79
98
  end
80
99
 
81
100
  after_confirmation if saved
@@ -98,7 +117,7 @@ module Devise
98
117
  generate_confirmation_token!
99
118
  end
100
119
 
101
- opts = pending_reconfirmation? ? { :to => unconfirmed_email } : { }
120
+ opts = pending_reconfirmation? ? { to: unconfirmed_email } : { }
102
121
  send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
103
122
  end
104
123
 
@@ -151,8 +170,14 @@ module Devise
151
170
 
152
171
  protected
153
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
+
154
179
  # A callback method used to deliver confirmation
155
- # instructions on creation. This can be overriden
180
+ # instructions on creation. This can be overridden
156
181
  # in models to map to a nice sign up e-mail.
157
182
  def send_on_create_confirmation_instructions
158
183
  send_confirmation_instructions
@@ -166,7 +191,7 @@ module Devise
166
191
  # Checks if the confirmation for the user is within the limit time.
167
192
  # We do this by calculating if the difference between today and the
168
193
  # confirmation sent date does not exceed the confirm in time configured.
169
- # 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.
170
195
  #
171
196
  # Example:
172
197
  #
@@ -186,7 +211,10 @@ module Devise
186
211
  # confirmation_period_valid? # will always return true
187
212
  #
188
213
  def confirmation_period_valid?
189
- 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
190
218
  end
191
219
 
192
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -202,7 +230,7 @@ module Devise
202
230
  # confirmation_period_expired? # will always return false
203
231
  #
204
232
  def confirmation_period_expired?
205
- 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)
206
234
  end
207
235
 
208
236
  # Checks whether the record requires any confirmation.
@@ -216,39 +244,88 @@ module Devise
216
244
  end
217
245
 
218
246
  # Generates a new random token for confirmation, and stores
219
- # the time this token is being generated
247
+ # the time this token is being generated in confirmation_sent_at
220
248
  def generate_confirmation_token
221
- raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
222
- @raw_confirmation_token = raw
223
- self.confirmation_token = enc
224
- 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
225
255
  end
226
256
 
227
257
  def generate_confirmation_token!
228
- generate_confirmation_token && save(:validate => false)
258
+ generate_confirmation_token && save(validate: false)
229
259
  end
230
260
 
231
- def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
232
- @reconfirmation_required = true
233
- self.unconfirmed_email = self.email
234
- self.email = self.email_was
235
- 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
236
277
  end
237
278
 
238
- def postpone_email_change?
239
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && !self.email.blank?
240
- @bypass_confirmation_postpone = false
241
- 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
242
299
  end
243
300
 
244
301
  def reconfirmation_required?
245
- self.class.reconfirmable && @reconfirmation_required && !self.email.blank?
302
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
246
303
  end
247
304
 
248
305
  def send_confirmation_notification?
249
- confirmation_required? && !@skip_confirmation_notification && !self.email.blank?
306
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
250
307
  end
251
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
+
319
+ # A callback initiated after successfully confirming. This can be
320
+ # used to insert your own logic that is only run after the user successfully
321
+ # confirms.
322
+ #
323
+ # Example:
324
+ #
325
+ # def after_confirmation
326
+ # self.update_attribute(:invite_code, nil)
327
+ # end
328
+ #
252
329
  def after_confirmation
253
330
  end
254
331
 
@@ -257,7 +334,7 @@ module Devise
257
334
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
258
335
  # field. If no user is found, returns a new user with an email not found error.
259
336
  # Options must contain the user email
260
- def send_confirmation_instructions(attributes={})
337
+ def send_confirmation_instructions(attributes = {})
261
338
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
262
339
  unless confirmable.try(:persisted?)
263
340
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
@@ -271,17 +348,35 @@ module Devise
271
348
  # If the user is already confirmed, create an error for the user
272
349
  # Options must have the confirmation_token
273
350
  def confirm_by_token(confirmation_token)
274
- original_token = confirmation_token
275
- 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
276
372
 
277
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
- confirmable.confirm! if confirmable.persisted?
279
- confirmable.confirmation_token = original_token
373
+ confirmable.confirm if confirmable.persisted?
280
374
  confirmable
281
375
  end
282
376
 
283
377
  # Find a record for confirmation by unconfirmed email field
284
378
  def find_by_unconfirmed_email_with_errors(attributes = {})
379
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
285
380
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
286
381
  unconfirmed_attributes = attributes.symbolize_keys
287
382
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)