devise 3.2.1 → 4.4.3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (254) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +58 -10
  3. data/CHANGELOG.md +199 -979
  4. data/CODE_OF_CONDUCT.md +22 -0
  5. data/CONTRIBUTING.md +73 -8
  6. data/Gemfile +19 -11
  7. data/Gemfile.lock +152 -119
  8. data/ISSUE_TEMPLATE.md +19 -0
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +347 -93
  11. data/Rakefile +4 -2
  12. data/app/controllers/devise/confirmations_controller.rb +11 -5
  13. data/app/controllers/devise/omniauth_callbacks_controller.rb +12 -6
  14. data/app/controllers/devise/passwords_controller.rb +20 -8
  15. data/app/controllers/devise/registrations_controller.rb +34 -19
  16. data/app/controllers/devise/sessions_controller.rb +47 -17
  17. data/app/controllers/devise/unlocks_controller.rb +9 -4
  18. data/app/controllers/devise_controller.rb +67 -31
  19. data/app/helpers/devise_helper.rb +4 -2
  20. data/app/mailers/devise/mailer.rb +10 -0
  21. data/app/views/devise/confirmations/new.html.erb +8 -4
  22. data/app/views/devise/mailer/confirmation_instructions.html.erb +1 -1
  23. data/app/views/devise/mailer/email_changed.html.erb +7 -0
  24. data/app/views/devise/mailer/password_change.html.erb +3 -0
  25. data/app/views/devise/mailer/reset_password_instructions.html.erb +1 -1
  26. data/app/views/devise/mailer/unlock_instructions.html.erb +1 -1
  27. data/app/views/devise/passwords/edit.html.erb +15 -6
  28. data/app/views/devise/passwords/new.html.erb +8 -4
  29. data/app/views/devise/registrations/edit.html.erb +28 -14
  30. data/app/views/devise/registrations/new.html.erb +19 -8
  31. data/app/views/devise/sessions/new.html.erb +17 -8
  32. data/app/views/devise/shared/{_links.erb → _links.html.erb} +2 -2
  33. data/app/views/devise/unlocks/new.html.erb +8 -4
  34. data/bin/test +13 -0
  35. data/config/locales/en.yml +22 -17
  36. data/devise.gemspec +7 -6
  37. data/gemfiles/Gemfile.rails-4.1-stable +32 -0
  38. data/gemfiles/Gemfile.rails-4.1-stable.lock +171 -0
  39. data/gemfiles/Gemfile.rails-4.2-stable +32 -0
  40. data/gemfiles/Gemfile.rails-4.2-stable.lock +192 -0
  41. data/gemfiles/Gemfile.rails-5.0-stable +33 -0
  42. data/gemfiles/Gemfile.rails-5.0-stable.lock +192 -0
  43. data/gemfiles/Gemfile.rails-5.2-rc1 +26 -0
  44. data/gemfiles/Gemfile.rails-5.2-rc1.lock +201 -0
  45. data/guides/bug_report_templates/integration_test.rb +106 -0
  46. data/lib/devise.rb +107 -84
  47. data/lib/devise/controllers/helpers.rb +111 -31
  48. data/lib/devise/controllers/rememberable.rb +15 -6
  49. data/lib/devise/controllers/scoped_views.rb +3 -1
  50. data/lib/devise/controllers/sign_in_out.rb +39 -26
  51. data/lib/devise/controllers/store_location.rb +31 -2
  52. data/lib/devise/controllers/url_helpers.rb +9 -7
  53. data/lib/devise/delegator.rb +2 -0
  54. data/lib/devise/encryptor.rb +24 -0
  55. data/lib/devise/failure_app.rb +98 -39
  56. data/lib/devise/hooks/activatable.rb +7 -6
  57. data/lib/devise/hooks/csrf_cleaner.rb +5 -1
  58. data/lib/devise/hooks/forgetable.rb +2 -0
  59. data/lib/devise/hooks/lockable.rb +7 -2
  60. data/lib/devise/hooks/proxy.rb +4 -2
  61. data/lib/devise/hooks/rememberable.rb +4 -2
  62. data/lib/devise/hooks/timeoutable.rb +16 -9
  63. data/lib/devise/hooks/trackable.rb +3 -1
  64. data/lib/devise/mailers/helpers.rb +15 -12
  65. data/lib/devise/mapping.rb +8 -2
  66. data/lib/devise/models.rb +3 -1
  67. data/lib/devise/models/authenticatable.rb +63 -36
  68. data/lib/devise/models/confirmable.rb +121 -41
  69. data/lib/devise/models/database_authenticatable.rb +66 -23
  70. data/lib/devise/models/lockable.rb +30 -17
  71. data/lib/devise/models/omniauthable.rb +3 -1
  72. data/lib/devise/models/recoverable.rb +62 -26
  73. data/lib/devise/models/registerable.rb +2 -0
  74. data/lib/devise/models/rememberable.rb +62 -33
  75. data/lib/devise/models/timeoutable.rb +4 -8
  76. data/lib/devise/models/trackable.rb +12 -3
  77. data/lib/devise/models/validatable.rb +16 -9
  78. data/lib/devise/modules.rb +12 -10
  79. data/lib/devise/omniauth.rb +2 -0
  80. data/lib/devise/omniauth/config.rb +2 -0
  81. data/lib/devise/omniauth/url_helpers.rb +14 -5
  82. data/lib/devise/orm/active_record.rb +5 -1
  83. data/lib/devise/orm/mongoid.rb +6 -2
  84. data/lib/devise/parameter_filter.rb +2 -0
  85. data/lib/devise/parameter_sanitizer.rb +131 -69
  86. data/lib/devise/rails.rb +10 -13
  87. data/lib/devise/rails/routes.rb +147 -116
  88. data/lib/devise/rails/warden_compat.rb +3 -10
  89. data/lib/devise/secret_key_finder.rb +25 -0
  90. data/lib/devise/strategies/authenticatable.rb +20 -9
  91. data/lib/devise/strategies/base.rb +3 -1
  92. data/lib/devise/strategies/database_authenticatable.rb +8 -5
  93. data/lib/devise/strategies/rememberable.rb +15 -3
  94. data/lib/devise/test/controller_helpers.rb +165 -0
  95. data/lib/devise/test/integration_helpers.rb +63 -0
  96. data/lib/devise/test_helpers.rb +7 -124
  97. data/lib/devise/time_inflector.rb +4 -2
  98. data/lib/devise/token_generator.rb +3 -41
  99. data/lib/devise/version.rb +3 -1
  100. data/lib/generators/active_record/devise_generator.rb +47 -10
  101. data/lib/generators/active_record/templates/migration.rb +9 -7
  102. data/lib/generators/active_record/templates/migration_existing.rb +9 -7
  103. data/lib/generators/devise/controllers_generator.rb +46 -0
  104. data/lib/generators/devise/devise_generator.rb +9 -5
  105. data/lib/generators/devise/install_generator.rb +22 -0
  106. data/lib/generators/devise/orm_helpers.rb +8 -19
  107. data/lib/generators/devise/views_generator.rb +51 -28
  108. data/lib/generators/mongoid/devise_generator.rb +22 -19
  109. data/lib/generators/templates/README +5 -12
  110. data/lib/generators/templates/controllers/README +14 -0
  111. data/lib/generators/templates/controllers/confirmations_controller.rb +30 -0
  112. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +30 -0
  113. data/lib/generators/templates/controllers/passwords_controller.rb +34 -0
  114. data/lib/generators/templates/controllers/registrations_controller.rb +62 -0
  115. data/lib/generators/templates/controllers/sessions_controller.rb +27 -0
  116. data/lib/generators/templates/controllers/unlocks_controller.rb +30 -0
  117. data/lib/generators/templates/devise.rb +64 -35
  118. data/lib/generators/templates/markerb/confirmation_instructions.markerb +1 -1
  119. data/lib/generators/templates/markerb/email_changed.markerb +7 -0
  120. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  121. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  122. data/lib/generators/templates/markerb/unlock_instructions.markerb +1 -1
  123. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +2 -2
  124. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +4 -4
  125. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +2 -2
  126. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +6 -6
  127. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +4 -4
  128. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +6 -6
  129. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +2 -2
  130. data/test/controllers/custom_registrations_controller_test.rb +42 -0
  131. data/test/controllers/custom_strategy_test.rb +10 -6
  132. data/test/controllers/helper_methods_test.rb +24 -0
  133. data/test/controllers/helpers_test.rb +88 -40
  134. data/test/controllers/inherited_controller_i18n_messages_test.rb +53 -0
  135. data/test/controllers/internal_helpers_test.rb +31 -22
  136. data/test/controllers/load_hooks_controller_test.rb +21 -0
  137. data/test/controllers/passwords_controller_test.rb +8 -5
  138. data/test/controllers/sessions_controller_test.rb +42 -33
  139. data/test/controllers/url_helpers_test.rb +13 -5
  140. data/test/delegator_test.rb +3 -1
  141. data/test/devise_test.rb +34 -19
  142. data/test/failure_app_test.rb +150 -42
  143. data/test/generators/active_record_generator_test.rb +58 -31
  144. data/test/generators/controllers_generator_test.rb +50 -0
  145. data/test/generators/devise_generator_test.rb +4 -2
  146. data/test/generators/install_generator_test.rb +16 -3
  147. data/test/generators/mongoid_generator_test.rb +5 -3
  148. data/test/generators/views_generator_test.rb +40 -2
  149. data/test/helpers/devise_helper_test.rb +20 -20
  150. data/test/integration/authenticatable_test.rb +134 -141
  151. data/test/integration/confirmable_test.rb +109 -67
  152. data/test/integration/database_authenticatable_test.rb +36 -23
  153. data/test/integration/http_authenticatable_test.rb +29 -20
  154. data/test/integration/lockable_test.rb +52 -49
  155. data/test/integration/mounted_engine_test.rb +38 -0
  156. data/test/integration/omniauthable_test.rb +30 -15
  157. data/test/integration/recoverable_test.rb +76 -61
  158. data/test/integration/registerable_test.rb +107 -91
  159. data/test/integration/rememberable_test.rb +82 -30
  160. data/test/integration/timeoutable_test.rb +48 -40
  161. data/test/integration/trackable_test.rb +15 -8
  162. data/test/mailers/confirmation_instructions_test.rb +16 -14
  163. data/test/mailers/email_changed_test.rb +132 -0
  164. data/test/mailers/mailer_test.rb +20 -0
  165. data/test/mailers/reset_password_instructions_test.rb +13 -11
  166. data/test/mailers/unlock_instructions_test.rb +12 -10
  167. data/test/mapping_test.rb +15 -6
  168. data/test/models/authenticatable_test.rb +15 -3
  169. data/test/models/confirmable_test.rb +190 -95
  170. data/test/models/database_authenticatable_test.rb +75 -41
  171. data/test/models/lockable_test.rb +115 -61
  172. data/test/models/omniauthable_test.rb +3 -1
  173. data/test/models/recoverable_test.rb +116 -37
  174. data/test/models/registerable_test.rb +3 -1
  175. data/test/models/rememberable_test.rb +95 -94
  176. data/test/models/serializable_test.rb +19 -8
  177. data/test/models/timeoutable_test.rb +10 -8
  178. data/test/models/trackable_test.rb +50 -1
  179. data/test/models/validatable_test.rb +24 -30
  180. data/test/models_test.rb +19 -8
  181. data/test/omniauth/config_test.rb +15 -11
  182. data/test/omniauth/url_helpers_test.rb +8 -9
  183. data/test/orm/active_record.rb +16 -2
  184. data/test/orm/mongoid.rb +4 -2
  185. data/test/parameter_sanitizer_test.rb +53 -57
  186. data/test/rails_app/app/active_record/admin.rb +2 -0
  187. data/test/rails_app/app/active_record/shim.rb +3 -1
  188. data/test/rails_app/app/active_record/user.rb +14 -0
  189. data/test/rails_app/app/active_record/user_on_engine.rb +9 -0
  190. data/test/rails_app/app/active_record/user_on_main_app.rb +9 -0
  191. data/test/rails_app/app/active_record/user_with_validations.rb +12 -0
  192. data/test/rails_app/app/active_record/user_without_email.rb +10 -0
  193. data/test/rails_app/app/controllers/admins/sessions_controller.rb +3 -1
  194. data/test/rails_app/app/controllers/admins_controller.rb +3 -6
  195. data/test/rails_app/app/controllers/application_controller.rb +7 -3
  196. data/test/rails_app/app/controllers/application_with_fake_engine.rb +32 -0
  197. data/test/rails_app/app/controllers/custom/registrations_controller.rb +33 -0
  198. data/test/rails_app/app/controllers/home_controller.rb +7 -1
  199. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +3 -1
  200. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +3 -1
  201. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +7 -5
  202. data/test/rails_app/app/controllers/users_controller.rb +8 -6
  203. data/test/rails_app/app/helpers/application_helper.rb +2 -0
  204. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +5 -0
  205. data/test/rails_app/app/mailers/users/mailer.rb +3 -10
  206. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +6 -0
  207. data/test/rails_app/app/mongoid/admin.rb +13 -11
  208. data/test/rails_app/app/mongoid/shim.rb +4 -2
  209. data/test/rails_app/app/mongoid/user.rb +30 -19
  210. data/test/rails_app/app/mongoid/user_on_engine.rb +41 -0
  211. data/test/rails_app/app/mongoid/user_on_main_app.rb +41 -0
  212. data/test/rails_app/app/mongoid/user_with_validations.rb +37 -0
  213. data/test/rails_app/app/mongoid/user_without_email.rb +35 -0
  214. data/test/rails_app/app/views/admins/sessions/new.html.erb +1 -1
  215. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -1
  216. data/test/rails_app/app/views/home/index.html.erb +1 -1
  217. data/test/rails_app/app/views/home/join.html.erb +1 -1
  218. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -1
  219. data/test/rails_app/app/views/layouts/application.html.erb +1 -1
  220. data/test/rails_app/config/application.rb +13 -5
  221. data/test/rails_app/config/boot.rb +17 -4
  222. data/test/rails_app/config/environment.rb +2 -0
  223. data/test/rails_app/config/environments/development.rb +2 -0
  224. data/test/rails_app/config/environments/production.rb +10 -2
  225. data/test/rails_app/config/environments/test.rb +14 -3
  226. data/test/rails_app/config/initializers/backtrace_silencers.rb +2 -0
  227. data/test/rails_app/config/initializers/devise.rb +22 -21
  228. data/test/rails_app/config/initializers/inflections.rb +2 -0
  229. data/test/rails_app/config/initializers/secret_token.rb +3 -6
  230. data/test/rails_app/config/initializers/session_store.rb +2 -0
  231. data/test/rails_app/config/routes.rb +67 -43
  232. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +16 -10
  233. data/test/rails_app/db/schema.rb +2 -0
  234. data/test/rails_app/lib/shared_admin.rb +10 -4
  235. data/test/rails_app/lib/shared_user.rb +4 -1
  236. data/test/rails_app/lib/shared_user_without_email.rb +28 -0
  237. data/test/rails_app/lib/shared_user_without_omniauth.rb +15 -0
  238. data/test/rails_test.rb +11 -0
  239. data/test/routes_test.rb +92 -61
  240. data/test/secret_key_finder_test.rb +97 -0
  241. data/test/support/action_controller/record_identifier.rb +12 -0
  242. data/test/support/assertions.rb +4 -14
  243. data/test/support/helpers.rb +23 -10
  244. data/test/support/http_method_compatibility.rb +53 -0
  245. data/test/support/integration.rb +19 -16
  246. data/test/support/mongoid.yml +6 -0
  247. data/test/support/webrat/integrations/rails.rb +11 -0
  248. data/test/{test_helpers_test.rb → test/controller_helpers_test.rb} +60 -40
  249. data/test/test/integration_helpers_test.rb +34 -0
  250. data/test/test_helper.rb +9 -0
  251. data/test/test_models.rb +8 -6
  252. metadata +123 -53
  253. data/gemfiles/Gemfile.rails-3.2.x +0 -31
  254. data/gemfiles/Gemfile.rails-3.2.x.lock +0 -159
@@ -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)
@@ -27,12 +30,12 @@ module Devise
27
30
 
28
31
  def headers_for(action, opts)
29
32
  headers = {
30
- :subject => subject_for(action),
31
- :to => resource.email,
32
- :from => mailer_sender(devise_mapping),
33
- :reply_to => mailer_reply_to(devise_mapping),
34
- :template_path => template_paths,
35
- :template_name => action
33
+ subject: subject_for(action),
34
+ to: resource.email,
35
+ from: mailer_sender(devise_mapping),
36
+ reply_to: mailer_reply_to(devise_mapping),
37
+ template_path: template_paths,
38
+ template_name: action
36
39
  }.merge(opts)
37
40
 
38
41
  @email = headers[:to]
@@ -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:
@@ -82,8 +85,8 @@ module Devise
82
85
  # subject: '...'
83
86
  #
84
87
  def subject_for(key)
85
- I18n.t(:"#{devise_mapping.name}_subject", :scope => [:devise, :mailer, key],
86
- :default => [:subject, key.to_s.humanize])
88
+ I18n.t(:"#{devise_mapping.name}_subject", scope: [:devise, :mailer, key],
89
+ default: [:subject, key.to_s.humanize])
87
90
  end
88
91
  end
89
92
  end
@@ -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
@@ -23,16 +25,18 @@ module Devise
23
25
  #
24
26
  class Mapping #:nodoc:
25
27
  attr_reader :singular, :scoped_path, :path, :controllers, :path_names,
26
- :class_name, :sign_out_via, :format, :used_routes, :used_helpers, :failure_app
28
+ :class_name, :sign_out_via, :format, :used_routes, :used_helpers,
29
+ :failure_app, :router_name
27
30
 
28
31
  alias :name :singular
29
32
 
30
33
  # Receives an object and find a scope for it. If a scope cannot be found,
31
34
  # raises an error. If a symbol is given, it's considered to be the scope.
32
35
  def self.find_scope!(obj)
36
+ obj = obj.devise_scope if obj.respond_to?(:devise_scope)
33
37
  case obj
34
38
  when String, Symbol
35
- return obj
39
+ return obj.to_sym
36
40
  when Class
37
41
  Devise.mappings.each_value { |m| return m.name if obj <= m.to }
38
42
  else
@@ -60,6 +64,8 @@ module Devise
60
64
  @sign_out_via = options[:sign_out_via] || Devise.sign_out_via
61
65
  @format = options[:format]
62
66
 
67
+ @router_name = options[:router_name]
68
+
63
69
  default_failure_app(options)
64
70
  default_controllers(options)
65
71
  default_path_names(options)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Devise
2
4
  module Models
3
5
  class MissingAttribute < StandardError
@@ -12,7 +14,7 @@ module Devise
12
14
 
13
15
  # Creates configuration values for Devise and for the given module.
14
16
  #
15
- # Devise::Models.config(Devise::Authenticatable, :stretches, 10)
17
+ # Devise::Models.config(Devise::Models::DatabaseAuthenticatable, :stretches)
16
18
  #
17
19
  # The line above creates:
18
20
  #
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_model/version'
1
4
  require 'devise/hooks/activatable'
2
5
  require 'devise/hooks/csrf_cleaner'
3
6
 
@@ -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?
@@ -56,10 +59,10 @@ module Devise
56
59
  BLACKLIST_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]
60
63
 
61
64
  included do
62
- class_attribute :devise_modules, :instance_writer => false
65
+ class_attribute :devise_modules, instance_writer: false
63
66
  self.devise_modules ||= []
64
67
 
65
68
  before_validation :downcase_keys
@@ -95,29 +98,31 @@ module Devise
95
98
  def authenticatable_salt
96
99
  end
97
100
 
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
101
+ # Redefine serializable_hash in models for more secure defaults.
102
+ # By default, it removes from the serializable model all attributes that
103
+ # are *not* accessible. You can remove this default by using :force_except
104
+ # and passing a new list of attributes you want to exempt. All attributes
105
+ # given to :except will simply add names to exempt to Devise internal list.
106
+ def serializable_hash(options = nil)
107
+ options = options.try(:dup) || {}
108
+ options[:except] = Array(options[:except])
109
+
110
+ if options[:force_except]
111
+ options[:except].concat Array(options[:force_except])
112
+ else
113
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
114
+ end
115
+
116
+ super(options)
117
+ end
118
+
119
+ # Redefine inspect using serializable_hash, to ensure we don't accidentally
120
+ # leak passwords into exceptions.
121
+ def inspect
122
+ inspection = serializable_hash.collect do |k,v|
123
+ "#{k}: #{respond_to?(:attribute_for_inspect) ? attribute_for_inspect(k) : v.inspect}"
124
+ end
125
+ "#<#{self.class} #{inspection.join(", ")}>"
121
126
  end
122
127
 
123
128
  protected
@@ -127,7 +132,7 @@ module Devise
127
132
  end
128
133
 
129
134
  # This is an internal method called every time Devise needs
130
- # to send a notification/mail. This can be overriden if you
135
+ # to send a notification/mail. This can be overridden if you
131
136
  # need to customize the e-mail delivery logic. For instance,
132
137
  # if you are using a queue to deliver e-mails (delayed job,
133
138
  # sidekiq, resque, etc), you must add the delivery to the queue
@@ -149,13 +154,25 @@ module Devise
149
154
  # if new_record? || changed?
150
155
  # pending_notifications << [notification, args]
151
156
  # else
152
- # devise_mailer.send(notification, self, *args).deliver
157
+ # message = devise_mailer.send(notification, self, *args)
158
+ # Remove once we move to Rails 4.2+ only.
159
+ # if message.respond_to?(:deliver_now)
160
+ # message.deliver_now
161
+ # else
162
+ # message.deliver
163
+ # end
153
164
  # end
154
165
  # end
155
166
  #
156
167
  # def send_pending_notifications
157
168
  # pending_notifications.each do |notification, args|
158
- # devise_mailer.send(notification, self, *args).deliver
169
+ # message = devise_mailer.send(notification, self, *args)
170
+ # Remove once we move to Rails 4.2+ only.
171
+ # if message.respond_to?(:deliver_now)
172
+ # message.deliver_now
173
+ # else
174
+ # message.deliver
175
+ # end
159
176
  # end
160
177
  #
161
178
  # # Empty the pending notifications array because the
@@ -170,7 +187,13 @@ module Devise
170
187
  # end
171
188
  #
172
189
  def send_devise_notification(notification, *args)
173
- devise_mailer.send(notification, self, *args).deliver
190
+ message = devise_mailer.send(notification, self, *args)
191
+ # Remove once we move to Rails 4.2+ only.
192
+ if message.respond_to?(:deliver_now)
193
+ message.deliver_now
194
+ else
195
+ message.deliver
196
+ end
174
197
  end
175
198
 
176
199
  def downcase_keys
@@ -231,11 +254,11 @@ module Devise
231
254
  # Example:
232
255
  #
233
256
  # def self.find_for_authentication(tainted_conditions)
234
- # find_first_by_auth_conditions(tainted_conditions, :active => true)
257
+ # find_first_by_auth_conditions(tainted_conditions, active: true)
235
258
  # end
236
259
  #
237
260
  # Finally, notice that Devise also queries for users in other scenarios
238
- # besides authentication, for example when retrieving an user to send
261
+ # besides authentication, for example when retrieving a user to send
239
262
  # an e-mail for password reset. In such cases, find_for_authentication
240
263
  # is not called.
241
264
  def find_for_authentication(tainted_conditions)
@@ -246,14 +269,18 @@ module Devise
246
269
  to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
247
270
  end
248
271
 
249
- # Find an initialize a record setting an error if it can't be found.
272
+ # Find or initialize a record setting an error if it can't be found.
250
273
  def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
251
274
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
252
275
  end
253
276
 
254
- # Find an initialize a group of attributes based on a list of required attributes.
277
+ # Find or initialize a record with group of attributes based on a list of required attributes.
255
278
  def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
256
- attributes = attributes.slice(*required_attributes)
279
+ attributes = if attributes.respond_to? :permit!
280
+ attributes.slice(*required_attributes).permit!.to_h.with_indifferent_access
281
+ else
282
+ attributes.with_indifferent_access.slice(*required_attributes)
283
+ end
257
284
  attributes.delete_if { |key, value| value.blank? }
258
285
 
259
286
  if attributes.size == required_attributes.size
@@ -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
  #
@@ -202,7 +227,7 @@ module Devise
202
227
  # confirmation_period_expired? # will always return false
203
228
  #
204
229
  def confirmation_period_expired?
205
- self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
230
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now.utc > self.confirmation_sent_at.utc + self.class.confirm_within)
206
231
  end
207
232
 
208
233
  # Checks whether the record requires any confirmation.
@@ -216,39 +241,88 @@ module Devise
216
241
  end
217
242
 
218
243
  # Generates a new random token for confirmation, and stores
219
- # the time this token is being generated
244
+ # the time this token is being generated in confirmation_sent_at
220
245
  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
246
+ if self.confirmation_token && !confirmation_period_expired?
247
+ @raw_confirmation_token = self.confirmation_token
248
+ else
249
+ self.confirmation_token = @raw_confirmation_token = Devise.friendly_token
250
+ self.confirmation_sent_at = Time.now.utc
251
+ end
225
252
  end
226
253
 
227
254
  def generate_confirmation_token!
228
- generate_confirmation_token && save(:validate => false)
255
+ generate_confirmation_token && save(validate: false)
229
256
  end
230
257
 
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
258
+ if Devise.activerecord51?
259
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
260
+ @reconfirmation_required = true
261
+ self.unconfirmed_email = self.email
262
+ self.email = self.email_in_database
263
+ self.confirmation_token = nil
264
+ generate_confirmation_token
265
+ end
266
+ else
267
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
268
+ @reconfirmation_required = true
269
+ self.unconfirmed_email = self.email
270
+ self.email = self.email_was
271
+ self.confirmation_token = nil
272
+ generate_confirmation_token
273
+ end
236
274
  end
237
275
 
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
276
+ if Devise.activerecord51?
277
+ def postpone_email_change?
278
+ postpone = self.class.reconfirmable &&
279
+ will_save_change_to_email? &&
280
+ !@bypass_confirmation_postpone &&
281
+ self.email.present? &&
282
+ (!@skip_reconfirmation_in_callback || !self.email_in_database.nil?)
283
+ @bypass_confirmation_postpone = false
284
+ postpone
285
+ end
286
+ else
287
+ def postpone_email_change?
288
+ postpone = self.class.reconfirmable &&
289
+ email_changed? &&
290
+ !@bypass_confirmation_postpone &&
291
+ self.email.present? &&
292
+ (!@skip_reconfirmation_in_callback || !self.email_was.nil?)
293
+ @bypass_confirmation_postpone = false
294
+ postpone
295
+ end
242
296
  end
243
297
 
244
298
  def reconfirmation_required?
245
- self.class.reconfirmable && @reconfirmation_required && !self.email.blank?
299
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
246
300
  end
247
301
 
248
302
  def send_confirmation_notification?
249
- confirmation_required? && !@skip_confirmation_notification && !self.email.blank?
303
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
304
+ end
305
+
306
+ # With reconfirmable, notify the original email when the user first
307
+ # requests the email change, instead of when the change is confirmed.
308
+ def send_email_changed_notification?
309
+ if self.class.reconfirmable
310
+ self.class.send_email_changed_notification && reconfirmation_required?
311
+ else
312
+ super
313
+ end
250
314
  end
251
315
 
316
+ # A callback initiated after successfully confirming. This can be
317
+ # used to insert your own logic that is only run after the user successfully
318
+ # confirms.
319
+ #
320
+ # Example:
321
+ #
322
+ # def after_confirmation
323
+ # self.update_attribute(:invite_code, nil)
324
+ # end
325
+ #
252
326
  def after_confirmation
253
327
  end
254
328
 
@@ -271,17 +345,23 @@ module Devise
271
345
  # If the user is already confirmed, create an error for the user
272
346
  # Options must have the confirmation_token
273
347
  def confirm_by_token(confirmation_token)
274
- original_token = confirmation_token
275
- confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
348
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
349
+ unless confirmable
350
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
351
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
352
+ end
353
+
354
+ # TODO: replace above lines with
355
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
356
+ # after enough time has passed that Devise clients do not use digested tokens
276
357
 
277
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
- confirmable.confirm! if confirmable.persisted?
279
- confirmable.confirmation_token = original_token
358
+ confirmable.confirm if confirmable.persisted?
280
359
  confirmable
281
360
  end
282
361
 
283
362
  # Find a record for confirmation by unconfirmed email field
284
363
  def find_by_unconfirmed_email_with_errors(attributes = {})
364
+ attributes = attributes.slice(*confirmation_keys).permit!.to_h if attributes.respond_to? :permit
285
365
  unconfirmed_required_attributes = confirmation_keys.map { |k| k == :email ? :unconfirmed_email : k }
286
366
  unconfirmed_attributes = attributes.symbolize_keys
287
367
  unconfirmed_attributes[:unconfirmed_email] = unconfirmed_attributes.delete(:email)