devise 1.2.1 → 1.3.0

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 (45) hide show
  1. data/.travis.yml +7 -1
  2. data/CHANGELOG.rdoc +21 -1
  3. data/Gemfile.lock +1 -1
  4. data/README.rdoc +4 -5
  5. data/app/controllers/devise/confirmations_controller.rb +14 -6
  6. data/app/controllers/devise/passwords_controller.rb +7 -6
  7. data/app/controllers/devise/registrations_controller.rb +12 -10
  8. data/app/controllers/devise/sessions_controller.rb +23 -1
  9. data/app/controllers/devise/unlocks_controller.rb +7 -6
  10. data/config/locales/en.yml +1 -0
  11. data/lib/devise.rb +7 -8
  12. data/lib/devise/controllers/helpers.rb +0 -6
  13. data/lib/devise/controllers/internal_helpers.rb +38 -2
  14. data/lib/devise/failure_app.rb +4 -10
  15. data/lib/devise/models.rb +27 -4
  16. data/lib/devise/models/authenticatable.rb +1 -13
  17. data/lib/devise/models/confirmable.rb +1 -1
  18. data/lib/devise/models/database_authenticatable.rb +2 -1
  19. data/lib/devise/models/recoverable.rb +41 -5
  20. data/lib/devise/models/validatable.rb +2 -3
  21. data/lib/devise/omniauth.rb +3 -3
  22. data/lib/devise/rails.rb +8 -25
  23. data/lib/devise/rails/routes.rb +8 -0
  24. data/lib/devise/rails/warden_compat.rb +2 -2
  25. data/lib/devise/schema.rb +8 -3
  26. data/lib/devise/strategies/authenticatable.rb +2 -1
  27. data/lib/devise/version.rb +1 -1
  28. data/lib/generators/devise/views_generator.rb +3 -9
  29. data/lib/generators/templates/devise.rb +11 -2
  30. data/test/controllers/internal_helpers_test.rb +15 -0
  31. data/test/controllers/sessions_controller_test.rb +17 -0
  32. data/test/devise_test.rb +0 -3
  33. data/test/integration/authenticatable_test.rb +27 -7
  34. data/test/integration/confirmable_test.rb +28 -0
  35. data/test/integration/lockable_test.rb +35 -1
  36. data/test/integration/recoverable_test.rb +37 -0
  37. data/test/integration/registerable_test.rb +45 -0
  38. data/test/models/database_authenticatable_test.rb +20 -2
  39. data/test/models/recoverable_test.rb +44 -9
  40. data/test/models/validatable_test.rb +3 -3
  41. data/test/models_test.rb +23 -0
  42. data/test/rails_app/config/initializers/devise.rb +7 -2
  43. data/test/rails_app/config/routes.rb +2 -0
  44. data/test/routes_test.rb +7 -0
  45. metadata +7 -5
@@ -65,20 +65,8 @@ module Devise
65
65
  end
66
66
  end
67
67
 
68
- def active?
69
- ActiveSupport::Deprecation.warn "[DEVISE] active? is deprecated, please use active_for_authentication? instead.", caller
70
- active_for_authentication?
71
- end
72
-
73
68
  def active_for_authentication?
74
- my_methods = self.class.instance_methods(false)
75
- if my_methods.include?("active?") || my_methods.include?(:active?)
76
- ActiveSupport::Deprecation.warn "[DEVISE] Overriding active? is deprecated to avoid conflicts. " \
77
- "Please use active_for_authentication? instead.", caller
78
- active?
79
- else
80
- true
81
- end
69
+ true
82
70
  end
83
71
 
84
72
  def inactive_message
@@ -84,7 +84,7 @@ module Devise
84
84
  # Checks if the confirmation for the user is within the limit time.
85
85
  # We do this by calculating if the difference between today and the
86
86
  # confirmation sent date does not exceed the confirm in time configured.
87
- # Confirm_in is a model configuration, must always be an integer value.
87
+ # Confirm_within is a model configuration, must always be an integer value.
88
88
  #
89
89
  # Example:
90
90
  #
@@ -22,7 +22,7 @@ module Devise
22
22
  included do
23
23
  attr_reader :password, :current_password
24
24
  attr_accessor :password_confirmation
25
- before_save :downcase_keys
25
+ before_validation :downcase_keys
26
26
  end
27
27
 
28
28
  # Generates password encryption based on the given value.
@@ -33,6 +33,7 @@ module Devise
33
33
 
34
34
  # Verifies whether an password (ie from sign in) is the user password.
35
35
  def valid_password?(password)
36
+ return false if encrypted_password.blank?
36
37
  bcrypt = ::BCrypt::Password.new(self.encrypted_password)
37
38
  password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
38
39
  Devise.secure_compare(password, self.encrypted_password)
@@ -35,15 +35,44 @@ module Devise
35
35
 
36
36
  # Resets reset password token and send reset password instructions by email
37
37
  def send_reset_password_instructions
38
- generate_reset_password_token!
38
+ generate_reset_password_token! if should_generate_token?
39
39
  ::Devise.mailer.reset_password_instructions(self).deliver
40
40
  end
41
41
 
42
+ # Checks if the reset password token sent is within the limit time.
43
+ # We do this by calculating if the difference between today and the
44
+ # sending date does not exceed the confirm in time configured.
45
+ # reset_password_within is a model configuration, must always be an integer value.
46
+ #
47
+ # Example:
48
+ #
49
+ # # reset_password_within = 1.day and reset_password_sent_at = today
50
+ # reset_password_period_valid? # returns true
51
+ #
52
+ # # reset_password_within = 5.days and reset_password_sent_at = 4.days.ago
53
+ # reset_password_period_valid? # returns true
54
+ #
55
+ # # reset_password_within = 5.days and reset_password_sent_at = 5.days.ago
56
+ # reset_password_period_valid? # returns false
57
+ #
58
+ # # reset_password_within = 0.days
59
+ # reset_password_period_valid? # will always return false
60
+ #
61
+ def reset_password_period_valid?
62
+ respond_to?(:reset_password_sent_at) && reset_password_sent_at &&
63
+ reset_password_sent_at.utc >= self.class.reset_password_within.ago
64
+ end
65
+
42
66
  protected
43
67
 
68
+ def should_generate_token?
69
+ reset_password_token.nil? || !reset_password_period_valid?
70
+ end
71
+
44
72
  # Generates a new random token for reset password
45
73
  def generate_reset_password_token
46
74
  self.reset_password_token = self.class.reset_password_token
75
+ self.reset_password_sent_at = Time.now.utc if respond_to?(:reset_password_sent_at=)
47
76
  end
48
77
 
49
78
  # Resets the reset password token with and save the record without
@@ -55,6 +84,7 @@ module Devise
55
84
  # Removes reset_password token
56
85
  def clear_reset_password_token
57
86
  self.reset_password_token = nil
87
+ self.reset_password_sent_at = nil if respond_to?(:reset_password_sent_at=)
58
88
  end
59
89
 
60
90
  module ClassMethods
@@ -73,18 +103,24 @@ module Devise
73
103
  generate_token(:reset_password_token)
74
104
  end
75
105
 
76
- # Attempt to find a user by it's reset_password_token to reset it's
77
- # password. If a user is found, reset it's password and automatically
106
+ # Attempt to find a user by it's reset_password_token to reset its
107
+ # password. If a user is found and token is still valid, reset its password and automatically
78
108
  # try saving the record. If not user is found, returns a new user
79
109
  # containing an error in reset_password_token attribute.
80
110
  # Attributes must contain reset_password_token, password and confirmation
81
111
  def reset_password_by_token(attributes={})
82
112
  recoverable = find_or_initialize_with_error_by(:reset_password_token, attributes[:reset_password_token])
83
- recoverable.reset_password!(attributes[:password], attributes[:password_confirmation]) if recoverable.persisted?
113
+ if recoverable.persisted?
114
+ if recoverable.reset_password_period_valid?
115
+ recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
116
+ else
117
+ recoverable.errors.add(:reset_password_token, :invalid)
118
+ end
119
+ end
84
120
  recoverable
85
121
  end
86
122
 
87
- Devise::Models.config(self, :reset_password_keys)
123
+ Devise::Models.config(self, :reset_password_keys, :reset_password_within)
88
124
  end
89
125
  end
90
126
  end
@@ -10,7 +10,7 @@ module Devise
10
10
  # Validatable adds the following options to devise_for:
11
11
  #
12
12
  # * +email_regexp+: the regular expression used to validate e-mails;
13
- # * +password_length+: a range expressing password length. Defaults to 6..20.
13
+ # * +password_length+: a range expressing password length. Defaults to 6..128.
14
14
  #
15
15
  module Validatable
16
16
  # All validations used by this module.
@@ -23,8 +23,7 @@ module Devise
23
23
 
24
24
  base.class_eval do
25
25
  validates_presence_of :email, :if => :email_required?
26
- validates_uniqueness_of :email, :scope => authentication_keys[1..-1],
27
- :case_sensitive => (case_insensitive_keys != false), :allow_blank => true
26
+ validates_uniqueness_of :email, :case_sensitive => (case_insensitive_keys != false), :allow_blank => true
28
27
  validates_format_of :email, :with => email_regexp, :allow_blank => true
29
28
 
30
29
  with_options :if => :password_required? do |v|
@@ -14,15 +14,15 @@ OmniAuth.config.path_prefix = nil
14
14
 
15
15
  OmniAuth.config.on_failure = Proc.new do |env|
16
16
  env['devise.mapping'] = Devise::Mapping.find_by_path!(env['PATH_INFO'], :path)
17
- controller_klass = "#{env['devise.mapping'].controllers[:omniauth_callbacks].camelize}Controller"
18
- controller_klass.constantize.action(:failure).call(env)
17
+ controller_name = ActiveSupport::Inflector.camelize(env['devise.mapping'].controllers[:omniauth_callbacks])
18
+ controller_klass = ActiveSupport::Inflector.constantize("#{controller_name}Controller")
19
+ controller_klass.action(:failure).call(env)
19
20
  end
20
21
 
21
22
  module Devise
22
23
  module OmniAuth
23
24
  autoload :Config, "devise/omniauth/config"
24
25
  autoload :UrlHelpers, "devise/omniauth/url_helpers"
25
- autoload :TestHelpers, "devise/omniauth/test_helpers"
26
26
 
27
27
  class << self
28
28
  delegate :short_circuit_authorizers!, :unshort_circuit_authorizers!,
@@ -17,11 +17,14 @@ module Devise
17
17
  Devise.include_helpers(Devise::Controllers)
18
18
  end
19
19
 
20
- initializer "devise.navigationals" do
21
- formats = Devise.navigational_formats
22
- if formats.include?(:"*/*") && formats.exclude?("*/*")
23
- puts "[DEVISE] We see the symbol :\"*/*\" in the navigational formats in your initializer " \
24
- "but not the string \"*/*\". Due to changes in latest Rails, please include the latter."
20
+ initializer "devise.auth_keys" do
21
+ if Devise.authentication_keys.size > 1
22
+ puts "[DEVISE] You are configuring Devise to use more than one authentication key. " \
23
+ "In previous versions, we automatically added #{Devise.authentication_keys[1..-1].inspect} " \
24
+ "as scope to your e-mail validation, but this was changed now. If you were relying in such " \
25
+ "behavior, you should remove :validatable from your models and add the validations manually. " \
26
+ "To get rid of this warning, you can comment config.authentication_keys in your initializer " \
27
+ "and pass the current values as key to the devise call in your model."
25
28
  end
26
29
  end
27
30
 
@@ -36,25 +39,5 @@ module Devise
36
39
  Devise.include_helpers(Devise::OmniAuth)
37
40
  end
38
41
  end
39
-
40
- initializer "devise.encryptor_check" do
41
- case Devise.encryptor
42
- when :bcrypt
43
- puts "[DEVISE] From version 1.2, there is no need to set your encryptor to bcrypt " \
44
- "since encryptors are only enabled if you include :encryptable in your models. " \
45
- "To update your app, please:\n\n" \
46
- "1) Remove config.encryptor from your initializer;\n" \
47
- "2) Add t.encryptable to your old migrations;\n" \
48
- "3) [Optional] Remove password_salt in a new recent migration. Bcrypt does not require it anymore.\n"
49
- when nil
50
- # Nothing to say
51
- else
52
- puts "[DEVISE] You are using #{Devise.encryptor} as encryptor. From version 1.2, " \
53
- "you need to explicitly add encryptable as dependency. To update your app, please:\n\n" \
54
- "1) Remove config.encryptor from your initializer;\n" \
55
- "2) Add t.encryptable to your old migrations;\n" \
56
- "3) Add `devise :encryptable, :encryptor => :#{Devise.encryptor}` to your models.\n"
57
- end
58
- end
59
42
  end
60
43
  end
@@ -99,6 +99,11 @@ module ActionDispatch::Routing
99
99
  #
100
100
  # devise_for :users, :skip => :sessions
101
101
  #
102
+ # * :only => the opposite of :skip, tell which controllers only to generate routes to:
103
+ #
104
+ # devise_for :users, :only => :sessions
105
+ #
106
+ #
102
107
  # ==== Scoping
103
108
  #
104
109
  # Following Rails 3 routes DSL, you can nest devise_for calls inside a scope:
@@ -173,6 +178,9 @@ module ActionDispatch::Routing
173
178
  end
174
179
 
175
180
  routes = mapping.routes
181
+ if options.has_key?(:only)
182
+ routes = Array(options.delete(:only)).map { |s| s.to_s.singularize.to_sym } & mapping.routes
183
+ end
176
184
  routes -= Array(options.delete(:skip)).map { |s| s.to_s.singularize.to_sym }
177
185
 
178
186
  devise_scope mapping.name do
@@ -21,14 +21,14 @@ class Warden::SessionSerializer
21
21
  def deserialize(keys)
22
22
  if keys.size == 2
23
23
  raise "Devise changed how it stores objects in session. If you are seeing this message, " <<
24
- "you can fix it by changing one character in your cookie secret or cleaning up your " <<
24
+ "you can fix it by changing one character in your secret_token or cleaning up your " <<
25
25
  "database sessions if you are using a db store."
26
26
  end
27
27
 
28
28
  klass, id, salt = keys
29
29
 
30
30
  begin
31
- record = klass.constantize.to_adapter.get(id)
31
+ record = ActiveSupport::Inflector.constantize(klass).to_adapter.get(id)
32
32
  record if record && record.authenticatable_salt == salt
33
33
  rescue NameError => e
34
34
  if e.message =~ /uninitialized constant/
@@ -15,7 +15,7 @@ module Devise
15
15
  def database_authenticatable(options={})
16
16
  null = options[:null] || false
17
17
  default = options.key?(:default) ? options[:default] : ("" if null == false)
18
- include_email = !self.respond_to?(:authentication_keys) || self.authentication_keys.include?(:email)
18
+ include_email = !respond_to?(:authentication_keys) || self.authentication_keys.include?(:email)
19
19
 
20
20
  apply_devise_schema :email, String, :null => null, :default => default if include_email
21
21
  apply_devise_schema :encrypted_password, String, :null => null, :default => default, :limit => 128
@@ -38,9 +38,14 @@ module Devise
38
38
  apply_devise_schema :confirmation_sent_at, DateTime
39
39
  end
40
40
 
41
- # Creates reset_password_token.
42
- def recoverable
41
+ # Creates reset_password_token and reset_password_sent_at.
42
+ #
43
+ # == Options
44
+ # * :reset_within - When true, adds a column that reset passwords within some date
45
+ def recoverable(options={})
46
+ use_within = options.fetch(:reset_within, Devise.reset_password_within.present?)
43
47
  apply_devise_schema :reset_password_token, String
48
+ apply_devise_schema :reset_password_sent_at, DateTime if use_within
44
49
  end
45
50
 
46
51
  # Creates remember_token and remember_created_at.
@@ -157,7 +157,8 @@ module Devise
157
157
  # becomes simply :database.
158
158
  def authenticatable_name
159
159
  @authenticatable_name ||=
160
- self.class.name.split("::").last.underscore.sub("_authenticatable", "").to_sym
160
+ ActiveSupport::Inflector.underscore(self.class.name.split("::").last).
161
+ sub("_authenticatable", "").to_sym
161
162
  end
162
163
  end
163
164
  end
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "1.2.1".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
@@ -9,17 +9,11 @@ module Devise
9
9
  argument :scope, :required => false, :default => nil,
10
10
  :desc => "The scope to copy views to"
11
11
 
12
- class_option :template_engine, :type => :string, :aliases => "-t",
13
- :desc => "Template engine for the views. Available options are 'erb', 'haml' and 'slim'."
12
+ # class_option :template_engine, :type => :string, :aliases => "-t",
13
+ # :desc => "Template engine for the views. Available options are 'erb', 'haml' and 'slim'."
14
14
 
15
15
  def copy_views
16
- template = options[:template_engine].to_s
17
- case template
18
- when "haml", "slim"
19
- warn "#{template} templates have been removed from Devise gem"
20
- else
21
- directory "devise", "app/views/#{scope || :devise}"
22
- end
16
+ directory "devise", "app/views/#{scope || :devise}"
23
17
  end
24
18
  end
25
19
  end
@@ -82,9 +82,13 @@ Devise.setup do |config|
82
82
  # to false if you are not using database authenticatable.
83
83
  config.use_salt_as_remember_token = true
84
84
 
85
+ # Options to be passed to the created cookie. For instance, you can set
86
+ # :secure => true in order to force SSL only cookies.
87
+ # config.cookie_options = {}
88
+
85
89
  # ==> Configuration for :validatable
86
- # Range for password length. Default is 6..20.
87
- # config.password_length = 6..20
90
+ # Range for password length. Default is 6..128.
91
+ # config.password_length = 6..128
88
92
 
89
93
  # Regex to use to validate the email address
90
94
  # config.email_regexp = /\A([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})\z/i
@@ -122,6 +126,11 @@ Devise.setup do |config|
122
126
  # Defines which key will be used when recovering the password for an account
123
127
  # config.reset_password_keys = [ :email ]
124
128
 
129
+ # Time interval you can reset your password with a reset password key.
130
+ # Don't put a too small interval or your users won't have the time to
131
+ # change their passwords.
132
+ config.reset_password_within = 2.hours
133
+
125
134
  # ==> Configuration for :encryptable
126
135
  # Allow you to use another encryption algorithm besides bcrypt (default). You can use
127
136
  # :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
@@ -45,6 +45,14 @@ class HelpersTest < ActionController::TestCase
45
45
  @controller.send :require_no_authentication
46
46
  end
47
47
 
48
+ test 'require no authentication sets a flash message' do
49
+ @mock_warden.expects(:authenticated?).with(:user).returns(true)
50
+ @mock_warden.expects(:user).with(:user).returns(User.new)
51
+ @controller.expects(:redirect_to).with(root_path)
52
+ @controller.send :require_no_authentication
53
+ assert flash[:alert] == I18n.t("devise.failure.already_authenticated")
54
+ end
55
+
48
56
  test 'signed in resource returns signed in resource for current scope' do
49
57
  @mock_warden.expects(:authenticate).with(:scope => :user).returns(User.new)
50
58
  assert_kind_of User, @controller.signed_in_resource
@@ -69,4 +77,11 @@ class HelpersTest < ActionController::TestCase
69
77
  assert flash[:notice] == 'non-blank'
70
78
  MyController.send(:protected, :set_flash_message)
71
79
  end
80
+
81
+ test 'navigational_formats not returning a wild card' do
82
+ MyController.send(:public, :navigational_formats)
83
+ Devise.navigational_formats = [:"*/*", :html]
84
+ assert_not @controller.navigational_formats.include?(:"*/*")
85
+ MyController.send(:protected, :navigational_formats)
86
+ end
72
87
  end
@@ -0,0 +1,17 @@
1
+ require 'test_helper'
2
+
3
+ class SessionsControllerTest < ActionController::TestCase
4
+ tests Devise::SessionsController
5
+ include Devise::TestHelpers
6
+
7
+ test "#create doesn't raise exception after Warden authentication fails " \
8
+ + "when TestHelpers included" do
9
+ request.env["devise.mapping"] = Devise.mappings[:user]
10
+ assert_nothing_raised(NoMethodError) do
11
+ post :create, :user => {
12
+ :email => "nosuchuser@example.com",
13
+ :password => "wevdude"
14
+ }
15
+ end
16
+ end
17
+ end
@@ -58,9 +58,6 @@ class DeviseTest < ActiveSupport::TestCase
58
58
  assert_equal :fruits, Devise::CONTROLLERS[:kivi]
59
59
  Devise::ALL.delete(:kivi)
60
60
  Devise::CONTROLLERS.delete(:kivi)
61
-
62
- assert_nothing_raised(Exception) { Devise.add_module(:authenticatable_again, :model => 'devise/model/authenticatable') }
63
- assert defined?(Devise::Models::AuthenticatableAgain)
64
61
  end
65
62
 
66
63
  test 'should complain when comparing empty or different sized passes' do
@@ -205,17 +205,16 @@ class AuthenticationRedirectTest < ActionController::IntegrationTest
205
205
  assert_nil session[:"user_return_to"]
206
206
  end
207
207
 
208
- test 'sign in with xml format returns xml response' do
209
- create_user
210
- post user_session_path(:format => 'xml', :user => {:email => "user@test.com", :password => '123456'})
211
- assert_response :success
212
- assert_match /<\?xml version="1.0" encoding="UTF-8"\?>/, response.body
213
- end
214
-
215
208
  test 'redirect to configured home path for a given scope after sign in' do
216
209
  sign_in_as_admin
217
210
  assert_equal "/admin_area/home", @request.path
218
211
  end
212
+
213
+ test 'require_no_authentication should set the already_authenticated flash message' do
214
+ sign_in_as_user
215
+ visit new_user_session_path
216
+ assert_equal flash[:alert], I18n.t("devise.failure.already_authenticated")
217
+ end
219
218
  end
220
219
 
221
220
  class AuthenticationSessionTest < ActionController::IntegrationTest
@@ -354,6 +353,27 @@ class AuthenticationOthersTest < ActionController::IntegrationTest
354
353
  assert warden.authenticated?(:user)
355
354
  assert_not warden.authenticated?(:admin)
356
355
  end
356
+
357
+ test 'sign in with xml format returns xml response' do
358
+ create_user
359
+ post user_session_path(:format => 'xml'), :user => {:email => "user@test.com", :password => '123456'}
360
+ assert_response :success
361
+ assert response.body.include? %(<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<user>)
362
+ end
363
+
364
+ test 'sign out with xml format returns ok response' do
365
+ sign_in_as_user
366
+ get destroy_user_session_path(:format => 'xml')
367
+ assert_response :ok
368
+ assert_not warden.authenticated?(:user)
369
+ end
370
+
371
+ test 'sign out with json format returns empty json response' do
372
+ sign_in_as_user
373
+ get destroy_user_session_path(:format => 'json')
374
+ assert_response :ok
375
+ assert_not warden.authenticated?(:user)
376
+ end
357
377
  end
358
378
 
359
379
  class AuthenticationRequestKeysTest < ActionController::IntegrationTest