devise 3.2.4 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +12 -5
  4. data/CHANGELOG.md +28 -1
  5. data/Gemfile +5 -5
  6. data/Gemfile.lock +98 -92
  7. data/README.md +22 -16
  8. data/app/controllers/devise/confirmations_controller.rb +1 -1
  9. data/app/controllers/devise/registrations_controller.rb +18 -5
  10. data/app/controllers/devise/sessions_controller.rb +32 -9
  11. data/app/controllers/devise_controller.rb +3 -3
  12. data/app/views/devise/registrations/new.html.erb +1 -1
  13. data/app/views/devise/sessions/new.html.erb +2 -2
  14. data/app/views/devise/shared/_links.erb +1 -1
  15. data/config/locales/en.yml +16 -15
  16. data/gemfiles/Gemfile.rails-3.2-stable +3 -3
  17. data/gemfiles/Gemfile.rails-3.2-stable.lock +166 -0
  18. data/gemfiles/Gemfile.rails-4.0-stable +4 -4
  19. data/gemfiles/Gemfile.rails-4.0-stable.lock +162 -0
  20. data/gemfiles/Gemfile.rails-head +7 -4
  21. data/gemfiles/Gemfile.rails-head.lock +190 -0
  22. data/lib/devise.rb +8 -4
  23. data/lib/devise/controllers/helpers.rb +77 -6
  24. data/lib/devise/controllers/sign_in_out.rb +0 -1
  25. data/lib/devise/controllers/store_location.rb +8 -2
  26. data/lib/devise/controllers/url_helpers.rb +3 -1
  27. data/lib/devise/failure_app.rb +6 -6
  28. data/lib/devise/hooks/activatable.rb +3 -4
  29. data/lib/devise/hooks/csrf_cleaner.rb +3 -1
  30. data/lib/devise/hooks/timeoutable.rb +8 -1
  31. data/lib/devise/mapping.rb +4 -1
  32. data/lib/devise/models/confirmable.rb +3 -3
  33. data/lib/devise/models/database_authenticatable.rb +7 -3
  34. data/lib/devise/models/lockable.rb +2 -2
  35. data/lib/devise/models/recoverable.rb +23 -7
  36. data/lib/devise/models/rememberable.rb +2 -2
  37. data/lib/devise/models/trackable.rb +4 -1
  38. data/lib/devise/rails/routes.rb +8 -6
  39. data/lib/devise/strategies/authenticatable.rb +7 -0
  40. data/lib/devise/version.rb +1 -1
  41. data/lib/generators/active_record/devise_generator.rb +19 -2
  42. data/lib/generators/templates/README +1 -1
  43. data/lib/generators/templates/devise.rb +3 -0
  44. data/script/cached-bundle +49 -0
  45. data/script/s3-put +71 -0
  46. data/test/controllers/custom_registrations_controller_test.rb +35 -0
  47. data/test/controllers/helpers_test.rb +35 -0
  48. data/test/controllers/internal_helpers_test.rb +1 -1
  49. data/test/controllers/passwords_controller_test.rb +1 -1
  50. data/test/devise_test.rb +18 -5
  51. data/test/failure_app_test.rb +40 -4
  52. data/test/generators/active_record_generator_test.rb +6 -0
  53. data/test/helpers/devise_helper_test.rb +3 -2
  54. data/test/integration/authenticatable_test.rb +19 -3
  55. data/test/integration/confirmable_test.rb +49 -9
  56. data/test/integration/http_authenticatable_test.rb +1 -1
  57. data/test/integration/lockable_test.rb +6 -6
  58. data/test/integration/recoverable_test.rb +5 -5
  59. data/test/integration/registerable_test.rb +32 -22
  60. data/test/integration/timeoutable_test.rb +8 -2
  61. data/test/integration/trackable_test.rb +2 -2
  62. data/test/mailers/confirmation_instructions_test.rb +3 -3
  63. data/test/mailers/reset_password_instructions_test.rb +3 -3
  64. data/test/mailers/unlock_instructions_test.rb +3 -3
  65. data/test/models/authenticatable_test.rb +1 -1
  66. data/test/models/lockable_test.rb +6 -0
  67. data/test/models/recoverable_test.rb +12 -0
  68. data/test/models/rememberable_test.rb +21 -6
  69. data/test/models/trackable_test.rb +28 -0
  70. data/test/models/validatable_test.rb +2 -2
  71. data/test/rails_app/app/active_record/user_on_engine.rb +7 -0
  72. data/test/rails_app/app/active_record/user_on_main_app.rb +7 -0
  73. data/test/rails_app/app/controllers/application_controller.rb +3 -0
  74. data/test/rails_app/app/controllers/application_with_fake_engine.rb +30 -0
  75. data/test/rails_app/app/controllers/custom/registrations_controller.rb +21 -0
  76. data/test/rails_app/app/controllers/users/omniauth_callbacks_controller.rb +1 -1
  77. data/test/rails_app/app/controllers/users_controller.rb +1 -1
  78. data/test/rails_app/app/mongoid/user_on_engine.rb +39 -0
  79. data/test/rails_app/app/mongoid/user_on_main_app.rb +39 -0
  80. data/test/rails_app/config/application.rb +1 -1
  81. data/test/rails_app/config/initializers/devise.rb +2 -0
  82. data/test/rails_app/config/routes.rb +17 -0
  83. data/test/rails_app/lib/shared_user.rb +1 -1
  84. data/test/rails_app/lib/shared_user_without_omniauth.rb +13 -0
  85. data/test/routes_test.rb +5 -3
  86. data/test/support/assertions.rb +2 -3
  87. data/test/support/integration.rb +2 -2
  88. data/test/test_helper.rb +2 -0
  89. data/test/test_helpers_test.rb +22 -32
  90. metadata +23 -2
@@ -72,7 +72,6 @@ module Devise
72
72
  def sign_out_all_scopes(lock=true)
73
73
  users = Devise.mappings.keys.map { |s| warden.user(scope: s, run_callbacks: false) }
74
74
 
75
- warden.raw_session.inspect
76
75
  warden.logout
77
76
  expire_data_after_sign_out!
78
77
  warden.clear_strategies_cache!
@@ -33,14 +33,20 @@ module Devise
33
33
  #
34
34
  def store_location_for(resource_or_scope, location)
35
35
  session_key = stored_location_key_for(resource_or_scope)
36
- if location
37
- uri = URI.parse(location)
36
+ uri = parse_uri(location)
37
+ if uri
38
38
  session[session_key] = [uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
39
39
  end
40
40
  end
41
41
 
42
42
  private
43
43
 
44
+ def parse_uri(location)
45
+ location && URI.parse(location)
46
+ rescue URI::InvalidURIError
47
+ nil
48
+ end
49
+
44
50
  def stored_location_key_for(resource_or_scope)
45
51
  scope = Devise::Mapping.find_scope!(resource_or_scope)
46
52
  "#{scope}_return_to"
@@ -47,7 +47,9 @@ module Devise
47
47
  class_eval <<-URL_HELPERS, __FILE__, __LINE__ + 1
48
48
  def #{method}(resource_or_scope, *args)
49
49
  scope = Devise::Mapping.find_scope!(resource_or_scope)
50
- _devise_route_context.send("#{action}\#{scope}_#{module_name}_#{path_or_url}", *args)
50
+ router_name = Devise.mappings[scope].router_name
51
+ context = router_name ? send(router_name) : _devise_route_context
52
+ context.send("#{action}\#{scope}_#{module_name}_#{path_or_url}", *args)
51
53
  end
52
54
  URL_HELPERS
53
55
  end
@@ -96,15 +96,15 @@ module Devise
96
96
  request.referrer
97
97
  end
98
98
 
99
- path || scope_path
99
+ path || scope_url
100
100
  else
101
- scope_path
101
+ scope_url
102
102
  end
103
103
  end
104
104
 
105
- def scope_path
105
+ def scope_url
106
106
  opts = {}
107
- route = :"new_#{scope}_session_path"
107
+ route = :"new_#{scope}_session_url"
108
108
  opts[:format] = request_format unless skip_format?
109
109
 
110
110
  config = Rails.application.config
@@ -114,8 +114,8 @@ module Devise
114
114
 
115
115
  if context.respond_to?(route)
116
116
  context.send(route, opts)
117
- elsif respond_to?(:root_path)
118
- root_path(opts)
117
+ elsif respond_to?(:root_url)
118
+ root_url(opts)
119
119
  else
120
120
  "/"
121
121
  end
@@ -1,7 +1,6 @@
1
- # Deny user access whenever their account is not active yet. All strategies that inherits from
2
- # Devise::Strategies::Authenticatable and uses the validate already check if the user is active_for_authentication?
3
- # before actively signing them in. However, we need this as hook to validate the user activity
4
- # in each request and in case the user is using other strategies beside Devise ones.
1
+ # Deny user access whenever their account is not active yet.
2
+ # We need this as hook to validate the user activity on each request
3
+ # and in case the user is using other strategies beside Devise ones.
5
4
  Warden::Manager.after_set_user do |record, warden, options|
6
5
  if record && record.respond_to?(:active_for_authentication?) && !record.active_for_authentication?
7
6
  scope = options[:scope]
@@ -1,5 +1,7 @@
1
1
  Warden::Manager.after_authentication do |record, warden, options|
2
- if Devise.clean_up_csrf_token_on_authentication
2
+ clean_up_for_winning_strategy = !warden.winning_strategy.respond_to?(:clean_up_csrf?) ||
3
+ warden.winning_strategy.clean_up_csrf?
4
+ if Devise.clean_up_csrf_token_on_authentication && clean_up_for_winning_strategy
3
5
  warden.request.session.try(:delete, :_csrf_token)
4
6
  end
5
7
  end
@@ -9,6 +9,13 @@ Warden::Manager.after_set_user do |record, warden, options|
9
9
 
10
10
  if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false
11
11
  last_request_at = warden.session(scope)['last_request_at']
12
+
13
+ if last_request_at.is_a? Integer
14
+ last_request_at = Time.at(last_request_at).utc
15
+ elsif last_request_at.is_a? String
16
+ last_request_at = Time.parse(last_request_at)
17
+ end
18
+
12
19
  proxy = Devise::Hooks::Proxy.new(warden)
13
20
 
14
21
  if record.timedout?(last_request_at) && !env['devise.skip_timeout']
@@ -22,7 +29,7 @@ Warden::Manager.after_set_user do |record, warden, options|
22
29
  end
23
30
 
24
31
  unless env['devise.skip_trackable']
25
- warden.session(scope)['last_request_at'] = Time.now.utc
32
+ warden.session(scope)['last_request_at'] = Time.now.utc.to_i
26
33
  end
27
34
  end
28
35
  end
@@ -23,7 +23,8 @@ 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
 
@@ -60,6 +61,8 @@ module Devise
60
61
  @sign_out_via = options[:sign_out_via] || Devise.sign_out_via
61
62
  @format = options[:format]
62
63
 
64
+ @router_name = options[:router_name]
65
+
63
66
  default_failure_app(options)
64
67
  default_controllers(options)
65
68
  default_path_names(options)
@@ -236,17 +236,17 @@ module Devise
236
236
  end
237
237
 
238
238
  def postpone_email_change?
239
- postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && !self.email.blank?
239
+ postpone = self.class.reconfirmable && email_changed? && !@bypass_confirmation_postpone && self.email.present?
240
240
  @bypass_confirmation_postpone = false
241
241
  postpone
242
242
  end
243
243
 
244
244
  def reconfirmation_required?
245
- self.class.reconfirmable && @reconfirmation_required && !self.email.blank?
245
+ self.class.reconfirmable && @reconfirmation_required && self.email.present?
246
246
  end
247
247
 
248
248
  def send_confirmation_notification?
249
- confirmation_required? && !@skip_confirmation_notification && !self.email.blank?
249
+ confirmation_required? && !@skip_confirmation_notification && self.email.present?
250
250
  end
251
251
 
252
252
  def after_confirmation
@@ -55,9 +55,13 @@ module Devise
55
55
  self.password = self.password_confirmation = nil
56
56
  end
57
57
 
58
- # Update record attributes when :current_password matches, otherwise returns
59
- # error on :current_password. It also automatically rejects :password and
60
- # :password_confirmation if they are blank.
58
+ # Update record attributes when :current_password matches, otherwise
59
+ # returns error on :current_password.
60
+ #
61
+ # This method also rejects the password field if it is blank (allowing
62
+ # users to change relevant information like the e-mail without changing
63
+ # their password). In case the password field is rejected, the confirmation
64
+ # is also rejected as long as it is also blank.
61
65
  def update_with_password(params, *options)
62
66
  current_password = params.delete(:current_password)
63
67
 
@@ -115,10 +115,10 @@ module Devise
115
115
  # leaks the existence of an account.
116
116
  if Devise.paranoid
117
117
  super
118
+ elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
119
+ :locked
118
120
  elsif lock_strategy_enabled?(:failed_attempts) && last_attempt?
119
121
  :last_attempt
120
- elsif lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?
121
- :locked
122
122
  else
123
123
  super
124
124
  end
@@ -45,14 +45,10 @@ module Devise
45
45
  # Resets reset password token and send reset password instructions by email.
46
46
  # Returns the token sent in the e-mail.
47
47
  def send_reset_password_instructions
48
- raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
48
+ token = set_reset_password_token
49
+ send_reset_password_instructions_notification(token)
49
50
 
50
- self.reset_password_token = enc
51
- self.reset_password_sent_at = Time.now.utc
52
- self.save(validate: false)
53
-
54
- send_devise_notification(:reset_password_instructions, raw, {})
55
- raw
51
+ token
56
52
  end
57
53
 
58
54
  # Checks if the reset password token sent is within the limit time.
@@ -90,7 +86,27 @@ module Devise
90
86
  def after_password_reset
91
87
  end
92
88
 
89
+ def set_reset_password_token
90
+ raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
91
+
92
+ self.reset_password_token = enc
93
+ self.reset_password_sent_at = Time.now.utc
94
+ self.save(validate: false)
95
+ raw
96
+ end
97
+
98
+ def send_reset_password_instructions_notification(token)
99
+ send_devise_notification(:reset_password_instructions, token, {})
100
+ end
101
+
93
102
  module ClassMethods
103
+ # Attempt to find a user by password reset token. If a user is found, return it
104
+ # If a user is not found, return nil
105
+ def with_reset_password_token(token)
106
+ reset_password_token = Devise.token_generator.digest(self, :reset_password_token, token)
107
+ to_adapter.find_first(reset_password_token: reset_password_token)
108
+ end
109
+
94
110
  # Attempt to find a user by its email. If a record is found, send new
95
111
  # password instructions to it. If user is not found, returns a new user
96
112
  # with an email not found error.
@@ -58,7 +58,7 @@ module Devise
58
58
  def forget_me!
59
59
  return unless persisted?
60
60
  self.remember_token = nil if respond_to?(:remember_token=)
61
- self.remember_created_at = nil
61
+ self.remember_created_at = nil if self.class.expire_all_remember_me_on_sign_out
62
62
  save(validate: false)
63
63
  end
64
64
 
@@ -122,7 +122,7 @@ module Devise
122
122
  end
123
123
  end
124
124
 
125
- Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options)
125
+ Devise::Models.config(self, :remember_for, :extend_remember_period, :rememberable_options, :expire_all_remember_me_on_sign_out)
126
126
  end
127
127
  end
128
128
  end
@@ -15,7 +15,7 @@ module Devise
15
15
  [:current_sign_in_at, :current_sign_in_ip, :last_sign_in_at, :last_sign_in_ip, :sign_in_count]
16
16
  end
17
17
 
18
- def update_tracked_fields!(request)
18
+ def update_tracked_fields(request)
19
19
  old_current, new_current = self.current_sign_in_at, Time.now.utc
20
20
  self.last_sign_in_at = old_current || new_current
21
21
  self.current_sign_in_at = new_current
@@ -26,7 +26,10 @@ module Devise
26
26
 
27
27
  self.sign_in_count ||= 0
28
28
  self.sign_in_count += 1
29
+ end
29
30
 
31
+ def update_tracked_fields!(request)
32
+ update_tracked_fields(request)
30
33
  save(validate: false) or raise "Devise trackable could not save #{inspect}." \
31
34
  "Please make sure a model using trackable can be saved at sign in."
32
35
  end
@@ -129,7 +129,8 @@ module ActionDispatch::Routing
129
129
  #
130
130
  # devise_for :users, module: "users"
131
131
  #
132
- # * skip: tell which controller you want to skip routes from being created:
132
+ # * skip: tell which controller you want to skip routes from being created.
133
+ # It accepts :all as an option, meaning it will not generate any route at all:
133
134
  #
134
135
  # devise_for :users, skip: :sessions
135
136
  #
@@ -153,6 +154,8 @@ module ActionDispatch::Routing
153
154
  #
154
155
  # * defaults: works the same as Rails' defaults
155
156
  #
157
+ # * router_name: allows application level router name to be overwritten for the current scope
158
+ #
156
159
  # ==== Scoping
157
160
  #
158
161
  # Following Rails 3 routes DSL, you can nest devise_for calls inside a scope:
@@ -224,7 +227,7 @@ module ActionDispatch::Routing
224
227
  raise_no_devise_method_error!(mapping.class_name) unless mapping.to.respond_to?(:devise)
225
228
  rescue NameError => e
226
229
  raise unless mapping.class_name == resource.to_s.classify
227
- warn "[WARNING] You provided devise_for #{resource.inspect} but there is " <<
230
+ warn "[WARNING] You provided devise_for #{resource.inspect} but there is " \
228
231
  "no model #{mapping.class_name} defined in your application"
229
232
  next
230
233
  rescue NoMethodError => e
@@ -234,13 +237,12 @@ module ActionDispatch::Routing
234
237
 
235
238
  if options[:controllers] && options[:controllers][:omniauth_callbacks]
236
239
  unless mapping.omniauthable?
237
- msg = "Mapping omniauth_callbacks on a resource that is not omniauthable\n"
238
- msg << "Please add `devise :omniauthable` to the `#{mapping.class_name}` model"
239
- raise msg
240
+ raise ArgumentError, "Mapping omniauth_callbacks on a resource that is not omniauthable\n" \
241
+ "Please add `devise :omniauthable` to the `#{mapping.class_name}` model"
240
242
  end
241
243
  end
242
244
 
243
- routes = mapping.used_routes
245
+ routes = mapping.used_routes
244
246
 
245
247
  devise_scope mapping.name do
246
248
  with_devise_exclusive_scope mapping.fullpath, mapping.name, options do
@@ -16,6 +16,13 @@ module Devise
16
16
  valid_for_params_auth? || valid_for_http_auth?
17
17
  end
18
18
 
19
+ # Override and set to false for things like OmniAuth that technically
20
+ # run through Authentication (user_set) very often, which would normally
21
+ # reset CSRF data in the session
22
+ def clean_up_csrf?
23
+ true
24
+ end
25
+
19
26
  private
20
27
 
21
28
  # Receives a resource and check if it is valid by calling valid_for_authentication?
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "3.2.4".freeze
2
+ VERSION = "3.3.0".freeze
3
3
  end
@@ -53,8 +53,8 @@ module ActiveRecord
53
53
  t.integer :sign_in_count, default: 0, null: false
54
54
  t.datetime :current_sign_in_at
55
55
  t.datetime :last_sign_in_at
56
- t.string :current_sign_in_ip
57
- t.string :last_sign_in_ip
56
+ t.#{ip_column} :current_sign_in_ip
57
+ t.#{ip_column} :last_sign_in_ip
58
58
 
59
59
  ## Confirmable
60
60
  # t.string :confirmation_token
@@ -68,6 +68,23 @@ module ActiveRecord
68
68
  # t.datetime :locked_at
69
69
  RUBY
70
70
  end
71
+
72
+ def ip_column
73
+ # Padded with spaces so it aligns nicely with the rest of the columns.
74
+ "%-8s" % (inet? ? "inet" : "string")
75
+ end
76
+
77
+ def inet?
78
+ rails4? && postgresql?
79
+ end
80
+
81
+ def rails4?
82
+ Rails.version.start_with? '4'
83
+ end
84
+
85
+ def postgresql?
86
+ ActiveRecord::Base.connection.adapter_name.downcase == "postgresql"
87
+ end
71
88
  end
72
89
  end
73
90
  end
@@ -6,7 +6,7 @@ Some setup you must do manually if you haven't yet:
6
6
  is an example of default_url_options appropriate for a development environment
7
7
  in config/environments/development.rb:
8
8
 
9
- config.action_mailer.default_url_options = { host: 'localhost:3000' }
9
+ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
10
10
 
11
11
  In production, :host should be set to the actual host of your application.
12
12
 
@@ -132,6 +132,9 @@ Devise.setup do |config|
132
132
  # The time the user will be remembered without asking for credentials again.
133
133
  # config.remember_for = 2.weeks
134
134
 
135
+ # Invalidates all the remember me tokens when the user signs out.
136
+ config.expire_all_remember_me_on_sign_out = true
137
+
135
138
  # If true, extends the user's remember period when remembered via cookie.
136
139
  # config.extend_remember_period = false
137
140
 
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: cached-bundle install --deployment
3
+ #
4
+ # After running `bundle`, caches the `vendor/bundle` directory to S3.
5
+ # On the next run, restores the cached directory before running `bundle`.
6
+ # When `Gemfile.lock` changes, the cache gets rebuilt.
7
+ #
8
+ # Requirements:
9
+ # - Gemfile.lock
10
+ # - TRAVIS_REPO_SLUG
11
+ # - TRAVIS_RUBY_VERSION
12
+ # - AMAZON_S3_BUCKET
13
+ # - script/s3-put
14
+ # - bundle
15
+ # - curl
16
+ #
17
+ # Author: Mislav Marohnić
18
+
19
+ set -e
20
+
21
+ compute_md5() {
22
+ local output="$(openssl md5)"
23
+ echo "${output##* }"
24
+ }
25
+
26
+ download() {
27
+ curl --tcp-nodelay -qsfL "$1" -o "$2"
28
+ }
29
+
30
+
31
+ gemfile="${BUNDLE_GEMFILE:-Gemfile}"
32
+ bundle_fullpath="$(dirname $gemfile)/vendor/bundle"
33
+ bundle_path=${bundle_fullpath#$PWD/}
34
+ gemfile_hash="$(compute_md5 <"${gemfile}.lock")"
35
+ cache_name="${TRAVIS_RUBY_VERSION}-${gemfile_hash}.tgz"
36
+ fetch_url="http://${AMAZON_S3_BUCKET}.s3.amazonaws.com/${TRAVIS_REPO_SLUG}/${cache_name}"
37
+
38
+ if download "$fetch_url" "$cache_name"; then
39
+ echo "Reusing cached bundle ${cache_name}"
40
+ tar xzf "$cache_name"
41
+ fi
42
+
43
+ bundle "$@"
44
+
45
+ if [ ! -f "$cache_name" ] && [ -n "$AMAZON_SECRET_ACCESS_KEY" ]; then
46
+ echo "Caching \`${bundle_path}' to S3"
47
+ tar czf "$cache_name" "$bundle_path"
48
+ script/s3-put "$cache_name" "${AMAZON_S3_BUCKET}:${TRAVIS_REPO_SLUG}/${cache_name}"
49
+ fi