devise 2.1.2 → 3.5.10

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 (242) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +39 -10
  3. data/.yardopts +9 -0
  4. data/{CHANGELOG.rdoc → CHANGELOG.md} +445 -112
  5. data/CODE_OF_CONDUCT.md +22 -0
  6. data/CONTRIBUTING.md +16 -0
  7. data/Gemfile +10 -15
  8. data/Gemfile.lock +151 -129
  9. data/MIT-LICENSE +1 -1
  10. data/README.md +256 -96
  11. data/Rakefile +4 -2
  12. data/app/controllers/devise/confirmations_controller.rb +15 -7
  13. data/app/controllers/devise/omniauth_callbacks_controller.rb +6 -2
  14. data/app/controllers/devise/passwords_controller.rb +33 -9
  15. data/app/controllers/devise/registrations_controller.rb +66 -26
  16. data/app/controllers/devise/sessions_controller.rb +52 -21
  17. data/app/controllers/devise/unlocks_controller.rb +11 -6
  18. data/app/controllers/devise_controller.rb +65 -58
  19. data/app/helpers/devise_helper.rb +2 -2
  20. data/app/mailers/devise/mailer.rb +19 -10
  21. data/app/views/devise/confirmations/new.html.erb +8 -4
  22. data/app/views/devise/mailer/confirmation_instructions.html.erb +2 -2
  23. data/app/views/devise/mailer/password_change.html.erb +3 -0
  24. data/app/views/devise/mailer/reset_password_instructions.html.erb +2 -2
  25. data/app/views/devise/mailer/unlock_instructions.html.erb +2 -2
  26. data/app/views/devise/passwords/edit.html.erb +15 -6
  27. data/app/views/devise/passwords/new.html.erb +8 -4
  28. data/app/views/devise/registrations/edit.html.erb +29 -15
  29. data/app/views/devise/registrations/new.html.erb +19 -8
  30. data/app/views/devise/sessions/new.html.erb +17 -8
  31. data/app/views/devise/shared/{_links.erb → _links.html.erb} +4 -4
  32. data/app/views/devise/unlocks/new.html.erb +8 -4
  33. data/config/locales/en.yml +51 -47
  34. data/devise.gemspec +8 -6
  35. data/devise.png +0 -0
  36. data/gemfiles/Gemfile.rails-3.2-stable +29 -0
  37. data/gemfiles/Gemfile.rails-3.2-stable.lock +172 -0
  38. data/gemfiles/Gemfile.rails-4.0-stable +30 -0
  39. data/gemfiles/Gemfile.rails-4.0-stable.lock +166 -0
  40. data/gemfiles/Gemfile.rails-4.1-stable +30 -0
  41. data/gemfiles/Gemfile.rails-4.1-stable.lock +171 -0
  42. data/gemfiles/Gemfile.rails-4.2-stable +30 -0
  43. data/gemfiles/Gemfile.rails-4.2-stable.lock +193 -0
  44. data/lib/devise/controllers/helpers.rb +126 -108
  45. data/lib/devise/controllers/rememberable.rb +19 -17
  46. data/lib/devise/controllers/scoped_views.rb +1 -1
  47. data/lib/devise/controllers/sign_in_out.rb +96 -0
  48. data/lib/devise/controllers/store_location.rb +58 -0
  49. data/lib/devise/controllers/url_helpers.rb +7 -7
  50. data/lib/devise/encryptor.rb +22 -0
  51. data/lib/devise/failure_app.rb +85 -25
  52. data/lib/devise/hooks/activatable.rb +5 -6
  53. data/lib/devise/hooks/csrf_cleaner.rb +7 -0
  54. data/lib/devise/hooks/forgetable.rb +1 -1
  55. data/lib/devise/hooks/lockable.rb +2 -2
  56. data/lib/devise/hooks/proxy.rb +21 -0
  57. data/lib/devise/hooks/rememberable.rb +5 -4
  58. data/lib/devise/hooks/timeoutable.rb +16 -8
  59. data/lib/devise/hooks/trackable.rb +1 -1
  60. data/lib/devise/mailers/helpers.rb +27 -23
  61. data/lib/devise/mapping.rb +11 -7
  62. data/lib/devise/models/authenticatable.rb +82 -66
  63. data/lib/devise/models/confirmable.rb +142 -55
  64. data/lib/devise/models/database_authenticatable.rb +59 -15
  65. data/lib/devise/models/lockable.rb +41 -30
  66. data/lib/devise/models/omniauthable.rb +3 -3
  67. data/lib/devise/models/recoverable.rb +56 -41
  68. data/lib/devise/models/rememberable.rb +65 -27
  69. data/lib/devise/models/timeoutable.rb +2 -8
  70. data/lib/devise/models/trackable.rb +6 -4
  71. data/lib/devise/models/validatable.rb +9 -9
  72. data/lib/devise/models.rb +4 -13
  73. data/lib/devise/modules.rb +10 -11
  74. data/lib/devise/omniauth/url_helpers.rb +2 -2
  75. data/lib/devise/orm/active_record.rb +1 -1
  76. data/lib/devise/orm/mongoid.rb +1 -1
  77. data/lib/devise/{param_filter.rb → parameter_filter.rb} +10 -11
  78. data/lib/devise/parameter_sanitizer.rb +99 -0
  79. data/lib/devise/rails/routes.rb +173 -115
  80. data/lib/devise/rails/warden_compat.rb +10 -31
  81. data/lib/devise/rails.rb +14 -12
  82. data/lib/devise/strategies/authenticatable.rb +26 -26
  83. data/lib/devise/strategies/base.rb +1 -1
  84. data/lib/devise/strategies/database_authenticatable.rb +8 -4
  85. data/lib/devise/strategies/rememberable.rb +15 -5
  86. data/lib/devise/test_helpers.rb +7 -5
  87. data/lib/devise/time_inflector.rb +14 -0
  88. data/lib/devise/token_generator.rb +70 -0
  89. data/lib/devise/version.rb +1 -1
  90. data/lib/devise.rb +110 -52
  91. data/lib/generators/active_record/devise_generator.rb +34 -18
  92. data/lib/generators/active_record/templates/migration.rb +5 -6
  93. data/lib/generators/active_record/templates/migration_existing.rb +5 -6
  94. data/lib/generators/devise/controllers_generator.rb +44 -0
  95. data/lib/generators/devise/devise_generator.rb +5 -3
  96. data/lib/generators/devise/install_generator.rb +5 -0
  97. data/lib/generators/devise/orm_helpers.rb +25 -6
  98. data/lib/generators/devise/views_generator.rb +52 -22
  99. data/lib/generators/mongoid/devise_generator.rb +21 -26
  100. data/lib/generators/templates/README +9 -5
  101. data/lib/generators/templates/controllers/README +14 -0
  102. data/lib/generators/templates/controllers/confirmations_controller.rb +28 -0
  103. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +28 -0
  104. data/lib/generators/templates/controllers/passwords_controller.rb +32 -0
  105. data/lib/generators/templates/controllers/registrations_controller.rb +60 -0
  106. data/lib/generators/templates/controllers/sessions_controller.rb +25 -0
  107. data/lib/generators/templates/controllers/unlocks_controller.rb +28 -0
  108. data/lib/generators/templates/devise.rb +80 -43
  109. data/lib/generators/templates/markerb/confirmation_instructions.markerb +2 -2
  110. data/lib/generators/templates/markerb/password_change.markerb +3 -0
  111. data/lib/generators/templates/markerb/reset_password_instructions.markerb +1 -1
  112. data/lib/generators/templates/markerb/unlock_instructions.markerb +2 -2
  113. data/lib/generators/templates/simple_form_for/confirmations/new.html.erb +3 -2
  114. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +4 -4
  115. data/lib/generators/templates/simple_form_for/passwords/new.html.erb +2 -2
  116. data/lib/generators/templates/simple_form_for/registrations/edit.html.erb +11 -6
  117. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +4 -4
  118. data/lib/generators/templates/simple_form_for/sessions/new.html.erb +6 -6
  119. data/lib/generators/templates/simple_form_for/unlocks/new.html.erb +3 -2
  120. data/script/cached-bundle +49 -0
  121. data/script/s3-put +71 -0
  122. data/test/controllers/custom_registrations_controller_test.rb +40 -0
  123. data/test/controllers/helper_methods_test.rb +21 -0
  124. data/test/controllers/helpers_test.rb +95 -32
  125. data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
  126. data/test/controllers/internal_helpers_test.rb +39 -14
  127. data/test/controllers/load_hooks_controller_test.rb +19 -0
  128. data/test/controllers/passwords_controller_test.rb +31 -0
  129. data/test/controllers/sessions_controller_test.rb +66 -6
  130. data/test/controllers/url_helpers_test.rb +10 -4
  131. data/test/delegator_test.rb +1 -1
  132. data/test/devise_test.rb +45 -10
  133. data/test/failure_app_test.rb +121 -27
  134. data/test/generators/active_record_generator_test.rb +48 -8
  135. data/test/generators/controllers_generator_test.rb +48 -0
  136. data/test/generators/devise_generator_test.rb +2 -2
  137. data/test/generators/mongoid_generator_test.rb +3 -3
  138. data/test/generators/views_generator_test.rb +54 -3
  139. data/test/helpers/devise_helper_test.rb +18 -20
  140. data/test/integration/authenticatable_test.rb +161 -65
  141. data/test/integration/confirmable_test.rb +146 -77
  142. data/test/integration/database_authenticatable_test.rb +43 -30
  143. data/test/integration/http_authenticatable_test.rb +30 -22
  144. data/test/integration/lockable_test.rb +64 -49
  145. data/test/integration/omniauthable_test.rb +17 -15
  146. data/test/integration/recoverable_test.rb +111 -70
  147. data/test/integration/registerable_test.rb +114 -79
  148. data/test/integration/rememberable_test.rb +87 -31
  149. data/test/integration/timeoutable_test.rb +77 -33
  150. data/test/integration/trackable_test.rb +5 -5
  151. data/test/mailers/confirmation_instructions_test.rb +28 -8
  152. data/test/mailers/reset_password_instructions_test.rb +21 -8
  153. data/test/mailers/unlock_instructions_test.rb +20 -6
  154. data/test/mapping_test.rb +12 -5
  155. data/test/models/authenticatable_test.rb +17 -1
  156. data/test/models/confirmable_test.rb +216 -62
  157. data/test/models/database_authenticatable_test.rb +129 -49
  158. data/test/models/lockable_test.rb +132 -45
  159. data/test/models/recoverable_test.rb +100 -54
  160. data/test/models/rememberable_test.rb +89 -94
  161. data/test/models/serializable_test.rb +12 -11
  162. data/test/models/timeoutable_test.rb +6 -1
  163. data/test/models/trackable_test.rb +28 -0
  164. data/test/models/validatable_test.rb +31 -21
  165. data/test/models_test.rb +22 -48
  166. data/test/omniauth/config_test.rb +4 -4
  167. data/test/omniauth/url_helpers_test.rb +7 -4
  168. data/test/orm/active_record.rb +1 -0
  169. data/test/orm/mongoid.rb +2 -3
  170. data/test/parameter_sanitizer_test.rb +81 -0
  171. data/test/rails_app/Rakefile +0 -4
  172. data/test/rails_app/app/active_record/shim.rb +1 -1
  173. data/test/rails_app/app/active_record/user_on_engine.rb +7 -0
  174. data/test/rails_app/app/active_record/user_on_main_app.rb +7 -0
  175. data/test/rails_app/app/active_record/user_without_email.rb +8 -0
  176. data/test/rails_app/app/controllers/admins/sessions_controller.rb +1 -1
  177. data/test/rails_app/app/controllers/admins_controller.rb +0 -5
  178. data/test/rails_app/app/controllers/application_controller.rb +6 -2
  179. data/test/rails_app/app/controllers/application_with_fake_engine.rb +30 -0
  180. data/test/rails_app/app/controllers/custom/registrations_controller.rb +31 -0
  181. data/test/rails_app/app/controllers/home_controller.rb +1 -1
  182. data/test/rails_app/app/controllers/publisher/registrations_controller.rb +1 -1
  183. data/test/rails_app/app/controllers/publisher/sessions_controller.rb +1 -1
  184. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +4 -4
  185. data/test/rails_app/app/controllers/users_controller.rb +12 -4
  186. data/test/rails_app/app/mailers/users/from_proc_mailer.rb +3 -0
  187. data/test/rails_app/app/mailers/users/mailer.rb +1 -1
  188. data/test/rails_app/app/mailers/users/reply_to_mailer.rb +4 -0
  189. data/test/rails_app/app/mongoid/admin.rb +12 -10
  190. data/test/rails_app/app/mongoid/shim.rb +4 -5
  191. data/test/rails_app/app/mongoid/user.rb +19 -22
  192. data/test/rails_app/app/mongoid/user_on_engine.rb +39 -0
  193. data/test/rails_app/app/mongoid/user_on_main_app.rb +39 -0
  194. data/test/rails_app/app/mongoid/user_without_email.rb +33 -0
  195. data/test/rails_app/app/views/admins/sessions/new.html.erb +1 -1
  196. data/test/rails_app/app/views/home/admin_dashboard.html.erb +1 -1
  197. data/test/rails_app/app/views/home/index.html.erb +1 -1
  198. data/test/rails_app/app/views/home/join.html.erb +1 -1
  199. data/test/rails_app/app/views/home/user_dashboard.html.erb +1 -1
  200. data/test/rails_app/app/views/layouts/application.html.erb +1 -1
  201. data/test/rails_app/app/views/users/edit_form.html.erb +1 -0
  202. data/test/rails_app/bin/bundle +3 -0
  203. data/test/rails_app/bin/rails +4 -0
  204. data/test/rails_app/bin/rake +4 -0
  205. data/test/rails_app/config/application.rb +4 -5
  206. data/test/rails_app/config/boot.rb +9 -3
  207. data/test/rails_app/config/environment.rb +2 -2
  208. data/test/rails_app/config/environments/development.rb +19 -7
  209. data/test/rails_app/config/environments/production.rb +68 -17
  210. data/test/rails_app/config/environments/test.rb +24 -16
  211. data/test/rails_app/config/initializers/devise.rb +22 -20
  212. data/test/rails_app/config/initializers/secret_token.rb +8 -2
  213. data/test/rails_app/config/initializers/session_store.rb +1 -0
  214. data/test/rails_app/config/routes.rb +71 -46
  215. data/test/rails_app/db/migrate/20100401102949_create_tables.rb +9 -12
  216. data/test/rails_app/db/schema.rb +21 -18
  217. data/test/rails_app/lib/shared_admin.rb +7 -4
  218. data/test/rails_app/lib/shared_user.rb +6 -3
  219. data/test/rails_app/lib/shared_user_without_email.rb +26 -0
  220. data/test/rails_app/lib/shared_user_without_omniauth.rb +13 -0
  221. data/test/rails_test.rb +9 -0
  222. data/test/routes_test.rb +94 -78
  223. data/test/support/action_controller/record_identifier.rb +10 -0
  224. data/test/support/assertions.rb +2 -3
  225. data/test/support/helpers.rb +18 -32
  226. data/test/support/integration.rb +17 -16
  227. data/test/support/locale/en.yml +4 -0
  228. data/test/support/mongoid.yml +6 -0
  229. data/test/test_helper.rb +8 -1
  230. data/test/test_helpers_test.rb +64 -20
  231. data/test/test_models.rb +33 -0
  232. data/test/time_helpers.rb +137 -0
  233. metadata +172 -51
  234. data/app/views/devise/_links.erb +0 -3
  235. data/gemfiles/Gemfile.rails-3.1.x +0 -35
  236. data/gemfiles/Gemfile.rails-3.1.x.lock +0 -167
  237. data/lib/devise/models/token_authenticatable.rb +0 -77
  238. data/lib/devise/strategies/token_authenticatable.rb +0 -56
  239. data/test/indifferent_hash.rb +0 -33
  240. data/test/integration/token_authenticatable_test.rb +0 -161
  241. data/test/models/token_authenticatable_test.rb +0 -55
  242. data/test/rails_app/script/rails +0 -10
@@ -23,23 +23,25 @@ module Devise
23
23
  #
24
24
  class Mapping #:nodoc:
25
25
  attr_reader :singular, :scoped_path, :path, :controllers, :path_names,
26
- :class_name, :sign_out_via, :format, :used_routes, :used_helpers, :failure_app
26
+ :class_name, :sign_out_via, :format, :used_routes, :used_helpers,
27
+ :failure_app, :router_name
27
28
 
28
29
  alias :name :singular
29
30
 
30
31
  # Receives an object and find a scope for it. If a scope cannot be found,
31
32
  # raises an error. If a symbol is given, it's considered to be the scope.
32
- def self.find_scope!(duck)
33
- case duck
33
+ def self.find_scope!(obj)
34
+ obj = obj.devise_scope if obj.respond_to?(:devise_scope)
35
+ case obj
34
36
  when String, Symbol
35
- return duck
37
+ return obj.to_sym
36
38
  when Class
37
- Devise.mappings.each_value { |m| return m.name if duck <= m.to }
39
+ Devise.mappings.each_value { |m| return m.name if obj <= m.to }
38
40
  else
39
- Devise.mappings.each_value { |m| return m.name if duck.is_a?(m.to) }
41
+ Devise.mappings.each_value { |m| return m.name if obj.is_a?(m.to) }
40
42
  end
41
43
 
42
- raise "Could not find a valid mapping for #{duck.inspect}"
44
+ raise "Could not find a valid mapping for #{obj.inspect}"
43
45
  end
44
46
 
45
47
  def self.find_by_path!(path, path_type=:fullpath)
@@ -60,6 +62,8 @@ module Devise
60
62
  @sign_out_via = options[:sign_out_via] || Devise.sign_out_via
61
63
  @format = options[:format]
62
64
 
65
+ @router_name = options[:router_name]
66
+
63
67
  default_failure_app(options)
64
68
  default_controllers(options)
65
69
  default_path_names(options)
@@ -1,4 +1,6 @@
1
+ require 'active_model/version'
1
2
  require 'devise/hooks/activatable'
3
+ require 'devise/hooks/csrf_cleaner'
2
4
 
3
5
  module Devise
4
6
  module Models
@@ -10,32 +12,33 @@ module Devise
10
12
  #
11
13
  # * +authentication_keys+: parameters used for authentication. By default [:email].
12
14
  #
15
+ # * +http_authentication_key+: map the username passed via HTTP Auth to this parameter. Defaults to
16
+ # the first element in +authentication_keys+.
17
+ #
13
18
  # * +request_keys+: parameters from the request object used for authentication.
14
19
  # By specifying a symbol (which should be a request method), it will automatically be
15
20
  # passed to find_for_authentication method and considered in your model lookup.
16
21
  #
17
22
  # For instance, if you set :request_keys to [:subdomain], :subdomain will be considered
18
- # as key on authentication. This can also be a hash where the value is a boolean expliciting
23
+ # as key on authentication. This can also be a hash where the value is a boolean specifying
19
24
  # if the value is required or not.
20
25
  #
21
- # * +http_authenticatable+: if this model allows http authentication. By default true.
26
+ # * +http_authenticatable+: if this model allows http authentication. By default false.
22
27
  # It also accepts an array specifying the strategies that should allow http.
23
28
  #
24
29
  # * +params_authenticatable+: if this model allows authentication through request params. By default true.
25
30
  # It also accepts an array specifying the strategies that should allow params authentication.
26
31
  #
27
32
  # * +skip_session_storage+: By default Devise will store the user in session.
28
- # You can skip storage for http and token auth by appending values to array:
29
- # :skip_session_storage => [:token_auth] or :skip_session_storage => [:http_auth, :token_auth],
30
- # by default is set to :skip_session_storage => [:http_auth].
33
+ # By default is set to skip_session_storage: [:http_auth].
31
34
  #
32
35
  # == active_for_authentication?
33
36
  #
34
37
  # After authenticating a user and in each request, Devise checks if your model is active by
35
- # calling model.active_for_authentication?. This method is overwriten by other devise modules. For instance,
38
+ # calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
36
39
  # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
37
40
  #
38
- # You overwrite this method yourself, but if you do, don't forget to call super:
41
+ # You can overwrite this method yourself, but if you do, don't forget to call super:
39
42
  #
40
43
  # def active_for_authentication?
41
44
  # super && special_condition_is_valid?
@@ -54,10 +57,10 @@ module Devise
54
57
  BLACKLIST_FOR_SERIALIZATION = [:encrypted_password, :reset_password_token, :reset_password_sent_at,
55
58
  :remember_created_at, :sign_in_count, :current_sign_in_at, :last_sign_in_at, :current_sign_in_ip,
56
59
  :last_sign_in_ip, :password_salt, :confirmation_token, :confirmed_at, :confirmation_sent_at,
57
- :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at, :authentication_token]
60
+ :remember_token, :unconfirmed_email, :failed_attempts, :unlock_token, :locked_at]
58
61
 
59
62
  included do
60
- class_attribute :devise_modules, :instance_writer => false
63
+ class_attribute :devise_modules, instance_writer: false
61
64
  self.devise_modules ||= []
62
65
 
63
66
  before_validation :downcase_keys
@@ -93,33 +96,22 @@ module Devise
93
96
  def authenticatable_salt
94
97
  end
95
98
 
96
- def headers_for(name)
97
- {}
98
- end
99
+ # Redefine serializable_hash in models for more secure defaults.
100
+ # By default, it removes from the serializable model all attributes that
101
+ # are *not* accessible. You can remove this default by using :force_except
102
+ # and passing a new list of attributes you want to exempt. All attributes
103
+ # given to :except will simply add names to exempt to Devise internal list.
104
+ def serializable_hash(options = nil)
105
+ options ||= {}
106
+ options[:except] = Array(options[:except])
107
+
108
+ if options[:force_except]
109
+ options[:except].concat Array(options[:force_except])
110
+ else
111
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
112
+ end
99
113
 
100
- array = %w(serializable_hash)
101
- # to_xml does not call serializable_hash on 3.1
102
- array << "to_xml" if Rails::VERSION::STRING[0,3] == "3.1"
103
-
104
- array.each do |method|
105
- class_eval <<-RUBY, __FILE__, __LINE__
106
- # Redefine to_xml and serializable_hash in models for more secure defaults.
107
- # By default, it removes from the serializable model all attributes that
108
- # are *not* accessible. You can remove this default by using :force_except
109
- # and passing a new list of attributes you want to exempt. All attributes
110
- # given to :except will simply add names to exempt to Devise internal list.
111
- def #{method}(options=nil)
112
- options ||= {}
113
- options[:except] = Array(options[:except])
114
-
115
- if options[:force_except]
116
- options[:except].concat Array(options[:force_except])
117
- else
118
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
119
- end
120
- super(options)
121
- end
122
- RUBY
114
+ super(options)
123
115
  end
124
116
 
125
117
  protected
@@ -129,7 +121,7 @@ module Devise
129
121
  end
130
122
 
131
123
  # This is an internal method called every time Devise needs
132
- # to send a notification/mail. This can be overriden if you
124
+ # to send a notification/mail. This can be overridden if you
133
125
  # need to customize the e-mail delivery logic. For instance,
134
126
  # if you are using a queue to deliver e-mails (delayed job,
135
127
  # sidekiq, resque, etc), you must add the delivery to the queue
@@ -144,14 +136,26 @@ module Devise
144
136
  #
145
137
  # protected
146
138
  #
147
- # def send_devise_notification(notification)
148
- # pending_notifications << notification
139
+ # def send_devise_notification(notification, *args)
140
+ # # If the record is new or changed then delay the
141
+ # # delivery until the after_commit callback otherwise
142
+ # # send now because after_commit will not be called.
143
+ # if new_record? || changed?
144
+ # pending_notifications << [notification, args]
145
+ # else
146
+ # devise_mailer.send(notification, self, *args).deliver
147
+ # end
149
148
  # end
150
149
  #
151
150
  # def send_pending_notifications
152
- # pending_notifications.each do |n|
153
- # devise_mailer.send(n, self).deliver
151
+ # pending_notifications.each do |notification, args|
152
+ # devise_mailer.send(notification, self, *args).deliver
154
153
  # end
154
+ #
155
+ # # Empty the pending notifications array because the
156
+ # # after_commit hook can be called multiple times which
157
+ # # could cause multiple emails to be sent.
158
+ # pending_notifications.clear
155
159
  # end
156
160
  #
157
161
  # def pending_notifications
@@ -159,21 +163,42 @@ module Devise
159
163
  # end
160
164
  # end
161
165
  #
162
- def send_devise_notification(notification)
163
- devise_mailer.send(notification, self).deliver
166
+ def send_devise_notification(notification, *args)
167
+ 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
164
174
  end
165
175
 
166
176
  def downcase_keys
167
- self.class.case_insensitive_keys.each { |k| self[k].try(:downcase!) }
177
+ self.class.case_insensitive_keys.each { |k| apply_to_attribute_or_variable(k, :downcase) }
168
178
  end
169
179
 
170
180
  def strip_whitespace
171
- self.class.strip_whitespace_keys.each { |k| self[k].try(:strip!) }
181
+ self.class.strip_whitespace_keys.each { |k| apply_to_attribute_or_variable(k, :strip) }
182
+ end
183
+
184
+ def apply_to_attribute_or_variable(attr, method)
185
+ if self[attr]
186
+ self[attr] = self[attr].try(method)
187
+
188
+ # Use respond_to? here to avoid a regression where globally
189
+ # configured strip_whitespace_keys or case_insensitive_keys were
190
+ # attempting to strip or downcase when a model didn't have the
191
+ # globally configured key.
192
+ elsif respond_to?(attr) && respond_to?("#{attr}=")
193
+ new_value = send(attr).try(method)
194
+ send("#{attr}=", new_value)
195
+ end
172
196
  end
173
197
 
174
198
  module ClassMethods
175
199
  Devise::Models.config(self, :authentication_keys, :request_keys, :strip_whitespace_keys,
176
- :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage)
200
+ :case_insensitive_keys, :http_authenticatable, :params_authenticatable, :skip_session_storage,
201
+ :http_authentication_key)
177
202
 
178
203
  def serialize_into_session(record)
179
204
  [record.to_key, record.authenticatable_salt]
@@ -199,37 +224,36 @@ module Devise
199
224
  # it may be wrapped as well. For instance, database authenticatable
200
225
  # provides a `find_for_database_authentication` that wraps a call to
201
226
  # this method. This allows you to customize both database authenticatable
202
- # or the whole authenticate stack by customize `find_for_authentication.`
227
+ # or the whole authenticate stack by customize `find_for_authentication.`
203
228
  #
204
229
  # Overwrite to add customized conditions, create a join, or maybe use a
205
230
  # namedscope to filter records while authenticating.
206
231
  # Example:
207
232
  #
208
- # def self.find_for_authentication(conditions={})
209
- # conditions[:active] = true
210
- # super
233
+ # def self.find_for_authentication(tainted_conditions)
234
+ # find_first_by_auth_conditions(tainted_conditions, active: true)
211
235
  # end
212
236
  #
213
237
  # Finally, notice that Devise also queries for users in other scenarios
214
238
  # besides authentication, for example when retrieving an user to send
215
239
  # an e-mail for password reset. In such cases, find_for_authentication
216
240
  # is not called.
217
- def find_for_authentication(conditions)
218
- find_first_by_auth_conditions(conditions)
241
+ def find_for_authentication(tainted_conditions)
242
+ find_first_by_auth_conditions(tainted_conditions)
219
243
  end
220
244
 
221
- def find_first_by_auth_conditions(conditions)
222
- to_adapter.find_first devise_param_filter.filter(conditions)
245
+ def find_first_by_auth_conditions(tainted_conditions, opts={})
246
+ to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
223
247
  end
224
248
 
225
- # Find an initialize a record setting an error if it can't be found.
249
+ # Find or initialize a record setting an error if it can't be found.
226
250
  def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
227
251
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
228
252
  end
229
253
 
230
- # Find an initialize a group of attributes based on a list of required attributes.
254
+ # Find or initialize a record with group of attributes based on a list of required attributes.
231
255
  def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
232
- attributes = attributes.slice(*required_attributes)
256
+ attributes = attributes.slice(*required_attributes).with_indifferent_access
233
257
  attributes.delete_if { |key, value| value.blank? }
234
258
 
235
259
  if attributes.size == required_attributes.size
@@ -251,16 +275,8 @@ module Devise
251
275
 
252
276
  protected
253
277
 
254
- def devise_param_filter
255
- @devise_param_filter ||= Devise::ParamFilter.new(case_insensitive_keys, strip_whitespace_keys)
256
- end
257
-
258
- # Generate a token by looping and ensuring does not already exist.
259
- def generate_token(column)
260
- loop do
261
- token = Devise.friendly_token
262
- break token unless to_adapter.find_first({ column => token })
263
- end
278
+ def devise_parameter_filter
279
+ @devise_parameter_filter ||= Devise::ParameterFilter.new(case_insensitive_keys, strip_whitespace_keys)
264
280
  end
265
281
  end
266
282
  end
@@ -5,35 +5,56 @@ module Devise
5
5
  # Confirmation instructions are sent to the user email after creating a
6
6
  # record and when manually requested by a new confirmation instruction request.
7
7
  #
8
+ # Confirmable tracks the following columns:
9
+ #
10
+ # * confirmation_token - A unique random token
11
+ # * confirmed_at - A timestamp when the user clicked the confirmation link
12
+ # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
13
+ # * unconfirmed_email - An email address copied from the email attr. After confirmation
14
+ # this value is copied to the email attr then cleared
15
+ #
8
16
  # == Options
9
17
  #
10
- # Confirmable adds the following options to devise_for:
18
+ # Confirmable adds the following options to +devise+:
11
19
  #
12
- # * +allow_unconfirmed_access_for+: the time you want to allow the user to access his account
20
+ # * +allow_unconfirmed_access_for+: the time you want to allow the user to access their account
13
21
  # before confirming it. After this period, the user access is denied. You can
14
22
  # use this to let your user access some features of your application without
15
23
  # confirming the account, but blocking it after a certain period (ie 7 days).
16
24
  # By default allow_unconfirmed_access_for is zero, it means users always have to confirm to sign in.
17
25
  # * +reconfirmable+: requires any email changes to be confirmed (exactly the same way as
18
26
  # 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
27
+ # db field to be setup (t.reconfirmable in migrations). Until confirmed, new email is
20
28
  # stored in unconfirmed email column, and copied to email column on successful
21
29
  # confirmation.
30
+ # * +confirm_within+: the time before a sent confirmation token becomes invalid.
31
+ # You can use this to force the user to confirm within a set period of time.
32
+ # Confirmable will not generate a new token if a repeat confirmation is requested
33
+ # during this time frame, unless the user's email changed too.
22
34
  #
23
35
  # == Examples
24
36
  #
25
- # User.find(1).confirm! # returns true unless it's already confirmed
37
+ # User.find(1).confirm # returns true unless it's already confirmed
26
38
  # User.find(1).confirmed? # true/false
27
39
  # User.find(1).send_confirmation_instructions # manually send instructions
28
40
  #
29
41
  module Confirmable
30
42
  extend ActiveSupport::Concern
43
+ include ActionView::Helpers::DateHelper
31
44
 
32
45
  included do
33
- before_create :generate_confirmation_token, :if => :confirmation_required?
34
- after_create :send_on_create_confirmation_instructions, :if => :confirmation_required?
35
- before_update :postpone_email_change_until_confirmation, :if => :postpone_email_change?
36
- after_update :send_confirmation_instructions, :if => :reconfirmation_required?
46
+ before_create :generate_confirmation_token, if: :confirmation_required?
47
+ after_create :send_on_create_confirmation_instructions, if: :send_confirmation_notification?
48
+ before_update :postpone_email_change_until_confirmation_and_regenerate_confirmation_token, if: :postpone_email_change?
49
+ after_update :send_reconfirmation_instructions, if: :reconfirmation_required?
50
+ end
51
+
52
+ def initialize(*args, &block)
53
+ @bypass_confirmation_postpone = false
54
+ @reconfirmation_required = false
55
+ @skip_confirmation_notification = false
56
+ @raw_confirmation_token = nil
57
+ super
37
58
  end
38
59
 
39
60
  def self.required_fields(klass)
@@ -45,24 +66,37 @@ module Devise
45
66
  # Confirm a user by setting it's confirmed_at to actual time. If the user
46
67
  # is already confirmed, add an error to email field. If the user is invalid
47
68
  # add errors
48
- def confirm!
69
+ def confirm(args={})
49
70
  pending_any_confirmation do
50
- self.confirmation_token = nil
71
+ if confirmation_period_expired?
72
+ self.errors.add(:email, :confirmation_period_expired,
73
+ period: Devise::TimeInflector.time_ago_in_words(self.class.confirm_within.ago))
74
+ return false
75
+ end
76
+
51
77
  self.confirmed_at = Time.now.utc
52
78
 
53
- if self.class.reconfirmable && unconfirmed_email.present?
79
+ saved = if self.class.reconfirmable && unconfirmed_email.present?
54
80
  skip_reconfirmation!
55
81
  self.email = unconfirmed_email
56
82
  self.unconfirmed_email = nil
57
83
 
58
84
  # We need to validate in such cases to enforce e-mail uniqueness
59
- save(:validate => true)
85
+ save(validate: true)
60
86
  else
61
- save(:validate => false)
87
+ save(validate: args[:ensure_valid] == true)
62
88
  end
89
+
90
+ after_confirmation if saved
91
+ saved
63
92
  end
64
93
  end
65
94
 
95
+ def confirm!(args={})
96
+ ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
97
+ confirm(args)
98
+ end
99
+
66
100
  # Verifies whether a user is confirmed or not
67
101
  def confirmed?
68
102
  !!confirmed_at
@@ -74,16 +108,28 @@ module Devise
74
108
 
75
109
  # Send confirmation instructions by email
76
110
  def send_confirmation_instructions
77
- self.confirmation_token = nil if reconfirmation_required?
111
+ unless @raw_confirmation_token
112
+ generate_confirmation_token!
113
+ end
114
+
115
+ opts = pending_reconfirmation? ? { to: unconfirmed_email } : { }
116
+ send_devise_notification(:confirmation_instructions, @raw_confirmation_token, opts)
117
+ end
118
+
119
+ def send_reconfirmation_instructions
78
120
  @reconfirmation_required = false
79
121
 
80
- generate_confirmation_token! if self.confirmation_token.blank?
81
- send_devise_notification(:confirmation_instructions)
122
+ unless @skip_confirmation_notification
123
+ send_confirmation_instructions
124
+ end
82
125
  end
83
126
 
84
- # Resend confirmation token. This method does not need to generate a new token.
85
- def resend_confirmation_token
86
- pending_any_confirmation { send_confirmation_instructions }
127
+ # Resend confirmation token.
128
+ # Regenerates the token if the period is expired.
129
+ def resend_confirmation_instructions
130
+ pending_any_confirmation do
131
+ send_confirmation_instructions
132
+ end
87
133
  end
88
134
 
89
135
  # Overwrites active_for_authentication? for confirmation
@@ -105,27 +151,26 @@ module Devise
105
151
  self.confirmed_at = Time.now.utc
106
152
  end
107
153
 
154
+ # Skips sending the confirmation/reconfirmation notification email after_create/after_update. Unlike
155
+ # #skip_confirmation!, record still requires confirmation.
156
+ def skip_confirmation_notification!
157
+ @skip_confirmation_notification = true
158
+ end
159
+
108
160
  # If you don't want reconfirmation to be sent, neither a code
109
161
  # to be generated, call skip_reconfirmation!
110
162
  def skip_reconfirmation!
111
- @bypass_postpone = true
112
- end
113
-
114
- def headers_for(action)
115
- headers = super
116
- if action == :confirmation_instructions && pending_reconfirmation?
117
- headers[:to] = unconfirmed_email
118
- end
119
- headers
163
+ @bypass_confirmation_postpone = true
120
164
  end
121
165
 
122
166
  protected
123
167
 
124
168
  # A callback method used to deliver confirmation
125
- # instructions on creation. This can be overriden
169
+ # instructions on creation. This can be overridden
126
170
  # in models to map to a nice sign up e-mail.
127
171
  def send_on_create_confirmation_instructions
128
- send_devise_notification(:confirmation_instructions)
172
+ send_confirmation_instructions
173
+ skip_reconfirmation!
129
174
  end
130
175
 
131
176
  # Callback to overwrite if confirmation is required or not.
@@ -152,13 +197,32 @@ module Devise
152
197
  # # allow_unconfirmed_access_for = 0.days
153
198
  # confirmation_period_valid? # will always return false
154
199
  #
200
+ # # allow_unconfirmed_access_for = nil
201
+ # confirmation_period_valid? # will always return true
202
+ #
155
203
  def confirmation_period_valid?
156
- confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago
204
+ self.class.allow_unconfirmed_access_for.nil? || (confirmation_sent_at && confirmation_sent_at.utc >= self.class.allow_unconfirmed_access_for.ago)
205
+ end
206
+
207
+ # Checks if the user confirmation happens before the token becomes invalid
208
+ # Examples:
209
+ #
210
+ # # confirm_within = 3.days and confirmation_sent_at = 2.days.ago
211
+ # confirmation_period_expired? # returns false
212
+ #
213
+ # # confirm_within = 3.days and confirmation_sent_at = 4.days.ago
214
+ # confirmation_period_expired? # returns true
215
+ #
216
+ # # confirm_within = nil
217
+ # confirmation_period_expired? # will always return false
218
+ #
219
+ def confirmation_period_expired?
220
+ self.class.confirm_within && self.confirmation_sent_at && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
157
221
  end
158
222
 
159
223
  # Checks whether the record requires any confirmation.
160
224
  def pending_any_confirmation
161
- if !confirmed? || pending_reconfirmation?
225
+ if (!confirmed? || pending_reconfirmation?)
162
226
  yield
163
227
  else
164
228
  self.errors.add(:email, :already_confirmed)
@@ -166,36 +230,55 @@ module Devise
166
230
  end
167
231
  end
168
232
 
169
- # Generates a new random token for confirmation, and stores the time
170
- # this token is being generated
233
+ # Generates a new random token for confirmation, and stores
234
+ # the time this token is being generated in confirmation_sent_at
171
235
  def generate_confirmation_token
172
- self.confirmation_token = self.class.confirmation_token
173
- self.confirmation_sent_at = Time.now.utc
236
+ if self.confirmation_token && !confirmation_period_expired?
237
+ @raw_confirmation_token = self.confirmation_token
238
+ else
239
+ raw, _ = Devise.token_generator.generate(self.class, :confirmation_token)
240
+ self.confirmation_token = @raw_confirmation_token = raw
241
+ self.confirmation_sent_at = Time.now.utc
242
+ end
174
243
  end
175
244
 
176
245
  def generate_confirmation_token!
177
- generate_confirmation_token && save(:validate => false)
178
- end
179
-
180
- def after_password_reset
181
- super
182
- confirm! unless confirmed?
246
+ generate_confirmation_token && save(validate: false)
183
247
  end
184
248
 
185
- def postpone_email_change_until_confirmation
249
+ def postpone_email_change_until_confirmation_and_regenerate_confirmation_token
186
250
  @reconfirmation_required = true
187
251
  self.unconfirmed_email = self.email
188
252
  self.email = self.email_was
253
+ self.confirmation_token = nil
254
+ generate_confirmation_token
189
255
  end
190
256
 
191
257
  def postpone_email_change?
192
- postpone = self.class.reconfirmable && email_changed? && !@bypass_postpone
193
- @bypass_postpone = nil
258
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
259
+ @bypass_confirmation_postpone = false
194
260
  postpone
195
261
  end
196
262
 
197
263
  def reconfirmation_required?
198
- self.class.reconfirmable && @reconfirmation_required
264
+ self.class.reconfirmable && @reconfirmation_required && (self.email.present? || self.unconfirmed_email.present?)
265
+ end
266
+
267
+ def send_confirmation_notification?
268
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
269
+ end
270
+
271
+ # A callback initiated after successfully confirming. This can be
272
+ # used to insert your own logic that is only run after the user successfully
273
+ # confirms.
274
+ #
275
+ # Example:
276
+ #
277
+ # def after_confirmation
278
+ # self.update_attribute(:invite_code, nil)
279
+ # end
280
+ #
281
+ def after_confirmation
199
282
  end
200
283
 
201
284
  module ClassMethods
@@ -208,7 +291,7 @@ module Devise
208
291
  unless confirmable.try(:persisted?)
209
292
  confirmable = find_or_initialize_with_errors(confirmation_keys, attributes, :not_found)
210
293
  end
211
- confirmable.resend_confirmation_token if confirmable.persisted?
294
+ confirmable.resend_confirmation_instructions if confirmable.persisted?
212
295
  confirmable
213
296
  end
214
297
 
@@ -217,14 +300,18 @@ module Devise
217
300
  # If the user is already confirmed, create an error for the user
218
301
  # Options must have the confirmation_token
219
302
  def confirm_by_token(confirmation_token)
220
- confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
221
- confirmable.confirm! if confirmable.persisted?
222
- confirmable
223
- end
303
+ confirmable = find_first_by_auth_conditions(confirmation_token: confirmation_token)
304
+ unless confirmable
305
+ confirmation_digest = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
306
+ confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_digest)
307
+ end
308
+
309
+ # TODO: replace above lines with
310
+ # confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
311
+ # after enough time has passed that Devise clients do not use digested tokens
224
312
 
225
- # Generate a token checking if one does not already exist in the database.
226
- def confirmation_token
227
- generate_token(:confirmation_token)
313
+ confirmable.confirm if confirmable.persisted?
314
+ confirmable
228
315
  end
229
316
 
230
317
  # Find a record for confirmation by unconfirmed email field
@@ -235,7 +322,7 @@ module Devise
235
322
  find_or_initialize_with_errors(unconfirmed_required_attributes, unconfirmed_attributes, :not_found)
236
323
  end
237
324
 
238
- Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable)
325
+ Devise::Models.config(self, :allow_unconfirmed_access_for, :confirmation_keys, :reconfirmable, :confirm_within)
239
326
  end
240
327
  end
241
328
  end