devise 4.1.1 → 5.0.4

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 (255) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +68 -111
  3. data/MIT-LICENSE +2 -1
  4. data/README.md +315 -98
  5. data/app/controllers/devise/confirmations_controller.rb +3 -0
  6. data/app/controllers/devise/omniauth_callbacks_controller.rb +7 -5
  7. data/app/controllers/devise/passwords_controller.rb +10 -2
  8. data/app/controllers/devise/registrations_controller.rb +42 -20
  9. data/app/controllers/devise/sessions_controller.rb +9 -7
  10. data/app/controllers/devise/unlocks_controller.rb +3 -0
  11. data/app/controllers/devise_controller.rb +19 -3
  12. data/app/helpers/devise_helper.rb +3 -23
  13. data/app/mailers/devise/mailer.rb +10 -4
  14. data/app/views/devise/confirmations/new.html.erb +3 -3
  15. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  16. data/app/views/devise/passwords/edit.html.erb +6 -6
  17. data/app/views/devise/passwords/new.html.erb +4 -4
  18. data/app/views/devise/registrations/edit.html.erb +13 -10
  19. data/app/views/devise/registrations/new.html.erb +9 -9
  20. data/app/views/devise/sessions/new.html.erb +8 -8
  21. data/app/views/devise/shared/_error_messages.html.erb +15 -0
  22. data/app/views/devise/shared/_links.html.erb +13 -13
  23. data/app/views/devise/unlocks/new.html.erb +3 -3
  24. data/config/locales/en.yml +5 -2
  25. data/lib/devise/controllers/helpers.rb +24 -9
  26. data/lib/devise/controllers/rememberable.rb +3 -1
  27. data/lib/devise/controllers/responder.rb +35 -0
  28. data/lib/devise/controllers/scoped_views.rb +2 -0
  29. data/lib/devise/controllers/sign_in_out.rb +31 -21
  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 +2 -0
  34. data/lib/devise/failure_app.rb +71 -38
  35. data/lib/devise/hooks/activatable.rb +3 -1
  36. data/lib/devise/hooks/csrf_cleaner.rb +8 -1
  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 +5 -3
  42. data/lib/devise/hooks/trackable.rb +2 -0
  43. data/lib/devise/mailers/helpers.rb +15 -18
  44. data/lib/devise/mapping.rb +4 -2
  45. data/lib/devise/models/authenticatable.rb +58 -44
  46. data/lib/devise/models/confirmable.rb +52 -14
  47. data/lib/devise/models/database_authenticatable.rb +52 -20
  48. data/lib/devise/models/lockable.rb +19 -5
  49. data/lib/devise/models/omniauthable.rb +4 -2
  50. data/lib/devise/models/recoverable.rb +22 -21
  51. data/lib/devise/models/registerable.rb +4 -0
  52. data/lib/devise/models/rememberable.rb +6 -4
  53. data/lib/devise/models/timeoutable.rb +3 -1
  54. data/lib/devise/models/trackable.rb +15 -1
  55. data/lib/devise/models/validatable.rb +10 -6
  56. data/lib/devise/models.rb +4 -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 +2 -51
  60. data/lib/devise/omniauth.rb +4 -5
  61. data/lib/devise/orm/active_record.rb +5 -1
  62. data/lib/devise/orm/mongoid.rb +6 -2
  63. data/lib/devise/orm.rb +80 -0
  64. data/lib/devise/parameter_filter.rb +4 -0
  65. data/lib/devise/parameter_sanitizer.rb +16 -58
  66. data/lib/devise/rails/routes.rb +12 -11
  67. data/lib/devise/rails/warden_compat.rb +2 -0
  68. data/lib/devise/rails.rb +16 -6
  69. data/lib/devise/strategies/authenticatable.rb +3 -1
  70. data/lib/devise/strategies/base.rb +2 -0
  71. data/lib/devise/strategies/database_authenticatable.rb +8 -1
  72. data/lib/devise/strategies/rememberable.rb +2 -0
  73. data/lib/devise/test/controller_helpers.rb +156 -0
  74. data/lib/devise/test/integration_helpers.rb +63 -0
  75. data/lib/devise/time_inflector.rb +2 -0
  76. data/lib/devise/token_generator.rb +2 -0
  77. data/lib/devise/version.rb +3 -1
  78. data/lib/devise.rb +69 -28
  79. data/lib/generators/active_record/devise_generator.rb +38 -16
  80. data/lib/generators/active_record/templates/migration.rb +3 -1
  81. data/lib/generators/active_record/templates/migration_existing.rb +2 -0
  82. data/lib/generators/devise/controllers_generator.rb +4 -2
  83. data/lib/generators/devise/devise_generator.rb +5 -3
  84. data/lib/generators/devise/install_generator.rb +3 -5
  85. data/lib/generators/devise/orm_helpers.rb +5 -3
  86. data/lib/generators/devise/views_generator.rb +8 -9
  87. data/lib/generators/mongoid/devise_generator.rb +7 -5
  88. data/lib/generators/templates/README +9 -8
  89. data/lib/generators/templates/controllers/confirmations_controller.rb +2 -0
  90. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +3 -1
  91. data/lib/generators/templates/controllers/passwords_controller.rb +2 -0
  92. data/lib/generators/templates/controllers/registrations_controller.rb +4 -2
  93. data/lib/generators/templates/controllers/sessions_controller.rb +3 -1
  94. data/lib/generators/templates/controllers/unlocks_controller.rb +2 -0
  95. data/lib/generators/templates/devise.rb +59 -11
  96. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  97. data/lib/generators/templates/markerb/password_change.markerb +2 -2
  98. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +5 -1
  99. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +10 -2
  100. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +5 -2
  101. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +12 -4
  102. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +11 -3
  103. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +7 -2
  104. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +4 -1
  105. metadata +23 -302
  106. data/.gitignore +0 -10
  107. data/.travis.yml +0 -44
  108. data/.yardopts +0 -9
  109. data/CODE_OF_CONDUCT.md +0 -22
  110. data/CONTRIBUTING.md +0 -16
  111. data/Gemfile +0 -30
  112. data/Gemfile.lock +0 -182
  113. data/Rakefile +0 -36
  114. data/bin/test +0 -13
  115. data/devise.gemspec +0 -26
  116. data/devise.png +0 -0
  117. data/gemfiles/Gemfile.rails-4.1-stable +0 -30
  118. data/gemfiles/Gemfile.rails-4.1-stable.lock +0 -170
  119. data/gemfiles/Gemfile.rails-4.2-stable +0 -30
  120. data/gemfiles/Gemfile.rails-4.2-stable.lock +0 -192
  121. data/gemfiles/Gemfile.rails-5.0-beta +0 -37
  122. data/gemfiles/Gemfile.rails-5.0-beta.lock +0 -199
  123. data/lib/devise/test_helpers.rb +0 -137
  124. data/test/controllers/custom_registrations_controller_test.rb +0 -40
  125. data/test/controllers/custom_strategy_test.rb +0 -64
  126. data/test/controllers/helper_methods_test.rb +0 -22
  127. data/test/controllers/helpers_test.rb +0 -316
  128. data/test/controllers/inherited_controller_i18n_messages_test.rb +0 -51
  129. data/test/controllers/internal_helpers_test.rb +0 -127
  130. data/test/controllers/load_hooks_controller_test.rb +0 -19
  131. data/test/controllers/passwords_controller_test.rb +0 -32
  132. data/test/controllers/sessions_controller_test.rb +0 -106
  133. data/test/controllers/url_helpers_test.rb +0 -65
  134. data/test/delegator_test.rb +0 -19
  135. data/test/devise_test.rb +0 -107
  136. data/test/failure_app_test.rb +0 -320
  137. data/test/generators/active_record_generator_test.rb +0 -83
  138. data/test/generators/controllers_generator_test.rb +0 -48
  139. data/test/generators/devise_generator_test.rb +0 -39
  140. data/test/generators/install_generator_test.rb +0 -24
  141. data/test/generators/mongoid_generator_test.rb +0 -23
  142. data/test/generators/views_generator_test.rb +0 -103
  143. data/test/helpers/devise_helper_test.rb +0 -49
  144. data/test/integration/authenticatable_test.rb +0 -698
  145. data/test/integration/confirmable_test.rb +0 -324
  146. data/test/integration/database_authenticatable_test.rb +0 -95
  147. data/test/integration/http_authenticatable_test.rb +0 -106
  148. data/test/integration/lockable_test.rb +0 -240
  149. data/test/integration/omniauthable_test.rb +0 -135
  150. data/test/integration/recoverable_test.rb +0 -347
  151. data/test/integration/registerable_test.rb +0 -357
  152. data/test/integration/rememberable_test.rb +0 -211
  153. data/test/integration/timeoutable_test.rb +0 -184
  154. data/test/integration/trackable_test.rb +0 -92
  155. data/test/mailers/confirmation_instructions_test.rb +0 -115
  156. data/test/mailers/reset_password_instructions_test.rb +0 -96
  157. data/test/mailers/unlock_instructions_test.rb +0 -91
  158. data/test/mapping_test.rb +0 -134
  159. data/test/models/authenticatable_test.rb +0 -23
  160. data/test/models/confirmable_test.rb +0 -511
  161. data/test/models/database_authenticatable_test.rb +0 -269
  162. data/test/models/lockable_test.rb +0 -350
  163. data/test/models/omniauthable_test.rb +0 -7
  164. data/test/models/recoverable_test.rb +0 -251
  165. data/test/models/registerable_test.rb +0 -7
  166. data/test/models/rememberable_test.rb +0 -169
  167. data/test/models/serializable_test.rb +0 -49
  168. data/test/models/timeoutable_test.rb +0 -51
  169. data/test/models/trackable_test.rb +0 -41
  170. data/test/models/validatable_test.rb +0 -119
  171. data/test/models_test.rb +0 -153
  172. data/test/omniauth/config_test.rb +0 -57
  173. data/test/omniauth/url_helpers_test.rb +0 -51
  174. data/test/orm/active_record.rb +0 -17
  175. data/test/orm/mongoid.rb +0 -13
  176. data/test/parameter_sanitizer_test.rb +0 -131
  177. data/test/rails_app/Rakefile +0 -6
  178. data/test/rails_app/app/active_record/admin.rb +0 -6
  179. data/test/rails_app/app/active_record/shim.rb +0 -2
  180. data/test/rails_app/app/active_record/user.rb +0 -7
  181. data/test/rails_app/app/active_record/user_on_engine.rb +0 -7
  182. data/test/rails_app/app/active_record/user_on_main_app.rb +0 -7
  183. data/test/rails_app/app/active_record/user_without_email.rb +0 -8
  184. data/test/rails_app/app/controllers/admins/sessions_controller.rb +0 -6
  185. data/test/rails_app/app/controllers/admins_controller.rb +0 -6
  186. data/test/rails_app/app/controllers/application_controller.rb +0 -11
  187. data/test/rails_app/app/controllers/application_with_fake_engine.rb +0 -30
  188. data/test/rails_app/app/controllers/custom/registrations_controller.rb +0 -31
  189. data/test/rails_app/app/controllers/home_controller.rb +0 -29
  190. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +0 -2
  191. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +0 -2
  192. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +0 -14
  193. data/test/rails_app/app/controllers/users_controller.rb +0 -31
  194. data/test/rails_app/app/helpers/application_helper.rb +0 -3
  195. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +0 -3
  196. data/test/rails_app/app/mailers/users/mailer.rb +0 -3
  197. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +0 -4
  198. data/test/rails_app/app/mongoid/admin.rb +0 -29
  199. data/test/rails_app/app/mongoid/shim.rb +0 -23
  200. data/test/rails_app/app/mongoid/user.rb +0 -39
  201. data/test/rails_app/app/mongoid/user_on_engine.rb +0 -39
  202. data/test/rails_app/app/mongoid/user_on_main_app.rb +0 -39
  203. data/test/rails_app/app/mongoid/user_without_email.rb +0 -33
  204. data/test/rails_app/app/views/admins/index.html.erb +0 -1
  205. data/test/rails_app/app/views/admins/sessions/new.html.erb +0 -2
  206. data/test/rails_app/app/views/home/admin_dashboard.html.erb +0 -1
  207. data/test/rails_app/app/views/home/index.html.erb +0 -1
  208. data/test/rails_app/app/views/home/join.html.erb +0 -1
  209. data/test/rails_app/app/views/home/private.html.erb +0 -1
  210. data/test/rails_app/app/views/home/user_dashboard.html.erb +0 -1
  211. data/test/rails_app/app/views/layouts/application.html.erb +0 -24
  212. data/test/rails_app/app/views/users/edit_form.html.erb +0 -1
  213. data/test/rails_app/app/views/users/index.html.erb +0 -1
  214. data/test/rails_app/app/views/users/mailer/confirmation_instructions.erb +0 -1
  215. data/test/rails_app/app/views/users/sessions/new.html.erb +0 -1
  216. data/test/rails_app/bin/bundle +0 -3
  217. data/test/rails_app/bin/rails +0 -4
  218. data/test/rails_app/bin/rake +0 -4
  219. data/test/rails_app/config/application.rb +0 -44
  220. data/test/rails_app/config/boot.rb +0 -14
  221. data/test/rails_app/config/database.yml +0 -18
  222. data/test/rails_app/config/environment.rb +0 -5
  223. data/test/rails_app/config/environments/development.rb +0 -30
  224. data/test/rails_app/config/environments/production.rb +0 -84
  225. data/test/rails_app/config/environments/test.rb +0 -46
  226. data/test/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  227. data/test/rails_app/config/initializers/devise.rb +0 -180
  228. data/test/rails_app/config/initializers/inflections.rb +0 -2
  229. data/test/rails_app/config/initializers/secret_token.rb +0 -3
  230. data/test/rails_app/config/initializers/session_store.rb +0 -1
  231. data/test/rails_app/config/routes.rb +0 -126
  232. data/test/rails_app/config.ru +0 -4
  233. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +0 -71
  234. data/test/rails_app/db/schema.rb +0 -55
  235. data/test/rails_app/lib/shared_admin.rb +0 -17
  236. data/test/rails_app/lib/shared_user.rb +0 -30
  237. data/test/rails_app/lib/shared_user_without_email.rb +0 -26
  238. data/test/rails_app/lib/shared_user_without_omniauth.rb +0 -13
  239. data/test/rails_app/public/404.html +0 -26
  240. data/test/rails_app/public/422.html +0 -26
  241. data/test/rails_app/public/500.html +0 -26
  242. data/test/rails_app/public/favicon.ico +0 -0
  243. data/test/rails_test.rb +0 -9
  244. data/test/routes_test.rb +0 -279
  245. data/test/support/action_controller/record_identifier.rb +0 -10
  246. data/test/support/assertions.rb +0 -39
  247. data/test/support/helpers.rb +0 -77
  248. data/test/support/http_method_compatibility.rb +0 -51
  249. data/test/support/integration.rb +0 -92
  250. data/test/support/locale/en.yml +0 -8
  251. data/test/support/mongoid.yml +0 -6
  252. data/test/support/webrat/integrations/rails.rb +0 -33
  253. data/test/test_helper.rb +0 -34
  254. data/test/test_helpers_test.rb +0 -178
  255. data/test/test_models.rb +0 -33
@@ -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,11 +21,11 @@ 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
- throw :warden, scope: scope, message: :timeout
28
+ throw :warden, scope: scope, message: :timeout, locale: options.fetch(:locale, I18n.locale)
27
29
  end
28
30
 
29
31
  unless env['devise.skip_trackable']
@@ -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)
@@ -30,28 +33,22 @@ module Devise
30
33
  subject: subject_for(action),
31
34
  to: resource.email,
32
35
  from: mailer_sender(devise_mapping),
33
- reply_to: mailer_reply_to(devise_mapping),
36
+ reply_to: mailer_sender(devise_mapping),
34
37
  template_path: template_paths,
35
38
  template_name: action
36
- }.merge(opts)
39
+ }
40
+ # Give priority to the mailer's default if they exists.
41
+ headers.delete(:from) if default_params[:from]
42
+ headers.delete(:reply_to) if default_params[:reply_to]
43
+
44
+ headers.merge!(opts)
37
45
 
38
46
  @email = headers[:to]
39
47
  headers
40
48
  end
41
49
 
42
- def mailer_reply_to(mapping)
43
- mailer_sender(mapping, :reply_to)
44
- end
45
-
46
- def mailer_from(mapping)
47
- mailer_sender(mapping, :from)
48
- end
49
-
50
- def mailer_sender(mapping, sender = :from)
51
- default_sender = default_params[sender]
52
- if default_sender.present?
53
- default_sender.respond_to?(:to_proc) ? instance_eval(&default_sender) : default_sender
54
- elsif Devise.mailer_sender.is_a?(Proc)
50
+ def mailer_sender(mapping)
51
+ if Devise.mailer_sender.is_a?(Proc)
55
52
  Devise.mailer_sender.call(mapping.name)
56
53
  else
57
54
  Devise.mailer_sender
@@ -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
@@ -28,7 +30,7 @@ module Devise
28
30
 
29
31
  alias :name :singular
30
32
 
31
- # Receives an object and find a scope for it. If a scope cannot be found,
33
+ # Receives an object and finds a scope for it. If a scope cannot be found,
32
34
  # raises an error. If a symbol is given, it's considered to be the scope.
33
35
  def self.find_scope!(obj)
34
36
  obj = obj.devise_scope if obj.respond_to?(:devise_scope)
@@ -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
@@ -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
 
@@ -8,7 +9,7 @@ module Devise
8
9
  #
9
10
  # == Options
10
11
  #
11
- # Authenticatable adds the following options to devise_for:
12
+ # Authenticatable adds the following options to +devise+:
12
13
  #
13
14
  # * +authentication_keys+: parameters used for authentication. By default [:email].
14
15
  #
@@ -54,7 +55,7 @@ module Devise
54
55
  module Authenticatable
55
56
  extend ActiveSupport::Concern
56
57
 
57
- BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
58
+ UNSAFE_ATTRIBUTES_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
58
59
  :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
59
60
  :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
60
61
  :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
@@ -102,18 +103,27 @@ 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[:except] = Array(options[:except])
106
+ options = options.try(:dup) || {}
107
+ options[:except] = Array(options[:except]).dup
107
108
 
108
109
  if options[:force_except]
109
110
  options[:except].concat Array(options[:force_except])
110
111
  else
111
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
112
+ options[:except].concat UNSAFE_ATTRIBUTES_FOR_SERIALIZATION
112
113
  end
113
114
 
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
  #
@@ -140,37 +152,47 @@ module Devise
140
152
  # # If the record is new or changed then delay the
141
153
  # # delivery until the after_commit callback otherwise
142
154
  # # send now because after_commit will not be called.
143
- # if new_record? || changed?
144
- # pending_notifications << [notification, args]
155
+ # # For Rails < 6 use `changed?` instead of `saved_changes?`.
156
+ # if new_record? || saved_changes?
157
+ # pending_devise_notifications << [notification, args]
145
158
  # else
146
- # devise_mailer.send(notification, self, *args).deliver
159
+ # render_and_send_devise_message(notification, *args)
147
160
  # end
148
161
  # end
149
162
  #
150
- # def send_pending_notifications
151
- # pending_notifications.each do |notification, args|
152
- # devise_mailer.send(notification, self, *args).deliver
163
+ # private
164
+ #
165
+ # def send_pending_devise_notifications
166
+ # pending_devise_notifications.each do |notification, args|
167
+ # render_and_send_devise_message(notification, *args)
153
168
  # end
154
169
  #
155
170
  # # Empty the pending notifications array because the
156
171
  # # after_commit hook can be called multiple times which
157
172
  # # could cause multiple emails to be sent.
158
- # pending_notifications.clear
173
+ # pending_devise_notifications.clear
159
174
  # end
160
175
  #
161
- # def pending_notifications
162
- # @pending_notifications ||= []
176
+ # def pending_devise_notifications
177
+ # @pending_devise_notifications ||= []
163
178
  # end
179
+ #
180
+ # def render_and_send_devise_message(notification, *args)
181
+ # message = devise_mailer.send(notification, self, *args)
182
+ #
183
+ # # Deliver later with Active Job's `deliver_later`
184
+ # if message.respond_to?(:deliver_later)
185
+ # message.deliver_later
186
+ # else
187
+ # message.deliver_now
188
+ # end
189
+ # end
190
+ #
164
191
  # end
165
192
  #
166
193
  def send_devise_notification(notification, *args)
167
194
  message = devise_mailer.send(notification, self, *args)
168
- # Remove once we move to Rails 4.2+ only.
169
- if message.respond_to?(:deliver_now)
170
- message.deliver_now
171
- else
172
- message.deliver
173
- end
195
+ message.deliver_now
174
196
  end
175
197
 
176
198
  def downcase_keys
@@ -235,46 +257,38 @@ module Devise
235
257
  # end
236
258
  #
237
259
  # Finally, notice that Devise also queries for users in other scenarios
238
- # besides authentication, for example when retrieving an user to send
260
+ # besides authentication, for example when retrieving a user to send
239
261
  # an e-mail for password reset. In such cases, find_for_authentication
240
262
  # is not called.
241
263
  def find_for_authentication(tainted_conditions)
242
264
  find_first_by_auth_conditions(tainted_conditions)
243
265
  end
244
266
 
245
- def find_first_by_auth_conditions(tainted_conditions, opts={})
267
+ def find_first_by_auth_conditions(tainted_conditions, opts = {})
246
268
  to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
247
269
  end
248
270
 
249
271
  # 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:
272
+ def find_or_initialize_with_error_by(attribute, value, error = :invalid) #:nodoc:
251
273
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
252
274
  end
253
275
 
254
276
  # 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 = if attributes.respond_to? :permit!
257
- attributes.slice(*required_attributes).permit!.to_h.with_indifferent_access
258
- else
259
- attributes.with_indifferent_access.slice(*required_attributes)
260
- end
261
- attributes.delete_if { |key, value| value.blank? }
277
+ def find_or_initialize_with_errors(required_attributes, attributes, error = :invalid) #:nodoc:
278
+ attributes.try(:permit!)
279
+ attributes = attributes.to_h.with_indifferent_access
280
+ .slice(*required_attributes)
281
+ .delete_if { |key, value| value.blank? }
262
282
 
263
283
  if attributes.size == required_attributes.size
264
- record = find_first_by_auth_conditions(attributes)
284
+ record = find_first_by_auth_conditions(attributes) and return record
265
285
  end
266
286
 
267
- unless record
268
- record = new
269
-
287
+ new(devise_parameter_filter.filter(attributes)).tap do |record|
270
288
  required_attributes.each do |key|
271
- value = attributes[key]
272
- record.send("#{key}=", value)
273
- record.errors.add(key, value.present? ? error : :blank)
289
+ record.errors.add(key, attributes[key].blank? ? :blank : error)
274
290
  end
275
291
  end
276
-
277
- record
278
292
  end
279
293
 
280
294
  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
@@ -26,7 +28,9 @@ module Devise
26
28
  # initial account confirmation) to be applied. Requires additional unconfirmed_email
27
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
@@ -43,8 +47,8 @@ module Devise
43
47
 
44
48
  included do
45
49
  before_create :generate_confirmation_token, if: :confirmation_required?
46
- after_create :skip_reconfirmation!, if: :send_confirmation_notification?
47
- if respond_to?(:after_commit) # ActiveRecord
50
+ after_create :skip_reconfirmation_in_callback!, if: :send_confirmation_notification?
51
+ if Devise::Orm.active_record?(self) # ActiveRecord
48
52
  after_commit :send_on_create_confirmation_instructions, on: :create, if: :send_confirmation_notification?
49
53
  after_commit :send_reconfirmation_instructions, on: :update, if: :reconfirmation_required?
50
54
  else # Mongoid
@@ -56,6 +60,7 @@ module Devise
56
60
 
57
61
  def initialize(*args, &block)
58
62
  @bypass_confirmation_postpone = false
63
+ @skip_reconfirmation_in_callback = false
59
64
  @reconfirmation_required = false
60
65
  @skip_confirmation_notification = false
61
66
  @raw_confirmation_token = nil
@@ -71,7 +76,7 @@ module Devise
71
76
  # Confirm a user by setting it's confirmed_at to actual time. If the user
72
77
  # is already confirmed, add an error to email field. If the user is invalid
73
78
  # add errors
74
- def confirm(args={})
79
+ def confirm(args = {})
75
80
  pending_any_confirmation do
76
81
  if confirmation_period_expired?
77
82
  self.errors.add(:email, :confirmation_period_expired,
@@ -97,11 +102,6 @@ module Devise
97
102
  end
98
103
  end
99
104
 
100
- def confirm!(args={})
101
- ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
102
- confirm(args)
103
- end
104
-
105
105
  # Verifies whether a user is confirmed or not
106
106
  def confirmed?
107
107
  !!confirmed_at
@@ -170,6 +170,12 @@ module Devise
170
170
 
171
171
  protected
172
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
+
173
179
  # A callback method used to deliver confirmation
174
180
  # instructions on creation. This can be overridden
175
181
  # in models to map to a nice sign up e-mail.
@@ -205,7 +211,10 @@ module Devise
205
211
  # confirmation_period_valid? # will always return true
206
212
  #
207
213
  def confirmation_period_valid?
208
- 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
209
218
  end
210
219
 
211
220
  # Checks if the user confirmation happens before the token becomes invalid
@@ -221,7 +230,7 @@ module Devise
221
230
  # confirmation_period_expired? # will always return false
222
231
  #
223
232
  def confirmation_period_expired?
224
- 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)
225
234
  end
226
235
 
227
236
  # Checks whether the record requires any confirmation.
@@ -251,14 +260,21 @@ module Devise
251
260
 
252
261
  def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
253
262
  @reconfirmation_required = true
263
+ # Force unconfirmed_email to be updated, even if the value hasn't changed, to prevent a
264
+ # race condition which could allow an attacker to confirm an email they don't own. See #5783.
265
+ devise_unconfirmed_email_will_change!
254
266
  self.unconfirmed_email = self.email
255
- self.email = self.email_was
267
+ self.email = self.devise_email_in_database
256
268
  self.confirmation_token = nil
257
269
  generate_confirmation_token
258
270
  end
259
271
 
260
272
  def postpone_email_change?
261
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
273
+ postpone = self.class.reconfirmable &&
274
+ devise_will_save_change_to_email? &&
275
+ !@bypass_confirmation_postpone &&
276
+ self.email.present? &&
277
+ (!@skip_reconfirmation_in_callback || !self.devise_email_in_database.nil?)
262
278
  @bypass_confirmation_postpone = false
263
279
  postpone
264
280
  end
@@ -271,6 +287,16 @@ module Devise
271
287
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
272
288
  end
273
289
 
290
+ # With reconfirmable, notify the original email when the user first
291
+ # requests the email change, instead of when the change is confirmed.
292
+ def send_email_changed_notification?
293
+ if self.class.reconfirmable
294
+ self.class.send_email_changed_notification && reconfirmation_required?
295
+ else
296
+ super
297
+ end
298
+ end
299
+
274
300
  # A callback initiated after successfully confirming. This can be
275
301
  # used to insert your own logic that is only run after the user successfully
276
302
  # confirms.
@@ -289,7 +315,7 @@ module Devise
289
315
  # confirmation instructions to it. If not, try searching for a user by unconfirmed_email
290
316
  # field. If no user is found, returns a new user with an email not found error.
291
317
  # Options must contain the user email
292
- def send_confirmation_instructions(attributes={})
318
+ def send_confirmation_instructions(attributes = {})
293
319
  confirmable = find_by_unconfirmed_email_with_errors(attributes) if reconfirmable
294
320
  unless confirmable.try(:persisted?)
295
321
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
@@ -303,7 +329,19 @@ module Devise
303
329
  # If the user is already confirmed, create an error for the user
304
330
  # Options must have the confirmation_token
305
331
  def confirm_by_token(confirmation_token)
332
+ # When the `confirmation_token` parameter is blank, if there are any users with a blank
333
+ # `confirmation_token` in the database, the first one would be confirmed here.
334
+ # The error is being manually added here to ensure no users are confirmed by mistake.
335
+ # This was done in the model for convenience, since validation errors are automatically
336
+ # displayed in the view.
337
+ if confirmation_token.blank?
338
+ confirmable = new
339
+ confirmable.errors.add(:confirmation_token, :blank)
340
+ return confirmable
341
+ end
342
+
306
343
  confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
344
+
307
345
  unless confirmable
308
346
  confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
309
347
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
@@ -1,24 +1,29 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'devise/strategies/database_authenticatable'
2
4
 
3
5
  module Devise
4
- def self.bcrypt(klass, password)
5
- ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
6
- Devise::Encryptor.digest(klass, password)
7
- end
8
-
9
6
  module Models
10
7
  # Authenticatable Module, responsible for hashing the password and
11
8
  # validating the authenticity of a user while signing in.
12
9
  #
10
+ # This module defines a `password=` method. This method will hash the argument
11
+ # and store it in the `encrypted_password` column, bypassing any pre-existing
12
+ # `password` column if it exists.
13
+ #
13
14
  # == Options
14
15
  #
15
- # DatabaseAuthenticatable adds the following options to devise_for:
16
+ # DatabaseAuthenticatable adds the following options to +devise+:
16
17
  #
17
18
  # * +pepper+: a random string used to provide a more secure hash. Use
18
- # `rake secret` to generate new keys.
19
+ # `rails secret` to generate new keys.
19
20
  #
20
21
  # * +stretches+: the cost given to bcrypt.
21
22
  #
23
+ # * +send_email_changed_notification+: notify original email when it changes.
24
+ #
25
+ # * +send_password_change_notification+: notify email when password changes.
26
+ #
22
27
  # == Examples
23
28
  #
24
29
  # User.find(1).valid_password?('password123') # returns true/false
@@ -27,12 +32,29 @@ module Devise
27
32
  extend ActiveSupport::Concern
28
33
 
29
34
  included do
35
+ after_update :send_email_changed_notification, if: :send_email_changed_notification?
30
36
  after_update :send_password_change_notification, if: :send_password_change_notification?
31
37
 
32
38
  attr_reader :password, :current_password
33
39
  attr_accessor :password_confirmation
34
40
  end
35
41
 
42
+ def initialize(*args, &block)
43
+ @skip_email_changed_notification = false
44
+ @skip_password_change_notification = false
45
+ super
46
+ end
47
+
48
+ # Skips sending the email changed notification after_update
49
+ def skip_email_changed_notification!
50
+ @skip_email_changed_notification = true
51
+ end
52
+
53
+ # Skips sending the password change notification after_update
54
+ def skip_password_change_notification!
55
+ @skip_password_change_notification = true
56
+ end
57
+
36
58
  def self.required_fields(klass)
37
59
  [:encrypted_password] + klass.authentication_keys
38
60
  end
@@ -62,7 +84,7 @@ module Devise
62
84
  # users to change relevant information like the e-mail without changing
63
85
  # their password). In case the password field is rejected, the confirmation
64
86
  # is also rejected as long as it is also blank.
65
- def update_with_password(params, *options)
87
+ def update_with_password(params)
66
88
  current_password = params.delete(:current_password)
67
89
 
68
90
  if params[:password].blank?
@@ -71,11 +93,11 @@ module Devise
71
93
  end
72
94
 
73
95
  result = if valid_password?(current_password)
74
- update_attributes(params, *options)
96
+ update(params)
75
97
  else
76
- self.assign_attributes(params, *options)
77
- self.valid?
78
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
98
+ assign_attributes(params)
99
+ valid?
100
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
79
101
  false
80
102
  end
81
103
 
@@ -90,16 +112,16 @@ module Devise
90
112
  #
91
113
  # Example:
92
114
  #
93
- # def update_without_password(params, *options)
115
+ # def update_without_password(params)
94
116
  # params.delete(:email)
95
117
  # super(params)
96
118
  # end
97
119
  #
98
- def update_without_password(params, *options)
120
+ def update_without_password(params)
99
121
  params.delete(:password)
100
122
  params.delete(:password_confirmation)
101
123
 
102
- result = update_attributes(params, *options)
124
+ result = update(params)
103
125
  clean_up_passwords
104
126
  result
105
127
  end
@@ -111,8 +133,8 @@ module Devise
111
133
  result = if valid_password?(current_password)
112
134
  destroy
113
135
  else
114
- self.valid?
115
- self.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
136
+ valid?
137
+ errors.add(:current_password, current_password.blank? ? :blank : :invalid)
116
138
  false
117
139
  end
118
140
 
@@ -137,6 +159,12 @@ module Devise
137
159
  encrypted_password[0,29] if encrypted_password
138
160
  end
139
161
 
162
+ # Send notification to user when email changes.
163
+ def send_email_changed_notification
164
+ send_devise_notification(:email_changed, to: devise_email_before_last_save)
165
+ end
166
+
167
+ # Send notification to user when password changes.
140
168
  def send_password_change_notification
141
169
  send_devise_notification(:password_change)
142
170
  end
@@ -146,18 +174,22 @@ module Devise
146
174
  # Hashes the password using bcrypt. Custom hash functions should override
147
175
  # this method to apply their own algorithm.
148
176
  #
149
- # See https://github.com/plataformatec/devise-encryptable for examples
177
+ # See https://github.com/heartcombo/devise-encryptable for examples
150
178
  # of other hashing engines.
151
179
  def password_digest(password)
152
180
  Devise::Encryptor.digest(self.class, password)
153
181
  end
154
182
 
183
+ def send_email_changed_notification?
184
+ self.class.send_email_changed_notification && devise_saved_change_to_email? && !@skip_email_changed_notification
185
+ end
186
+
155
187
  def send_password_change_notification?
156
- self.class.send_password_change_notification && encrypted_password_changed?
188
+ self.class.send_password_change_notification && devise_saved_change_to_encrypted_password? && !@skip_password_change_notification
157
189
  end
158
190
 
159
191
  module ClassMethods
160
- Devise::Models.config(self, :pepper, :stretches, :send_password_change_notification)
192
+ Devise::Models.config(self, :pepper, :stretches, :send_email_changed_notification, :send_password_change_notification)
161
193
 
162
194
  # We assume this method already gets the sanitized values from the
163
195
  # DatabaseAuthenticatable strategy. If you are using this method on