Compare commits

...

16 Commits

Author SHA1 Message Date
Marcos Ferreira
4be9389dcb Merge pull request #5032 from JanBussieck/pure-signed_in-method
Use warden’s `authenticated?` for query methods to avoid side effects
2019-09-19 14:32:41 -03:00
Jan Bussieck
2fde07b9be Fix test case descriptions, since we are testing proxying to 'authenticated?' 2019-09-19 17:18:02 +02:00
Jan Bussieck
70c5f4bfaf Use warden’s authenticated? for query methods to avoid side effects
Previously checks whether a certain scope is signed in were performed using warden’s
`authenticate?` or `authenticate` methods which would run the strategies and sign in the
scope if valid params were given. We want to remove this side effect from query methods.
2019-09-19 17:18:02 +02:00
Jeremy Wadsack
0a2e67878a When password reset token expires, redirect to new password path (#4837) 2019-09-19 10:22:30 -03:00
Leonardo Tegon
c381c916f3 Remove circular argument reference
/Users/tegon/src/public/plataformatec/devise/app/controllers/devise/sessions_controller.rb:75: warning: circular argument reference - status
2019-09-19 10:22:30 -03:00
Felipe Renan
9999072620 Update CHANGELOG.md [ci skip] 2019-09-19 10:22:30 -03:00
Marc Busqué
34238e9f18 Update trackable fields only in a database sign in
References #4584 and waiting-for-dev/devise-jwt#23

* Bug
Users that use devise-jwt, will not have the correct behavior of trackable
feature. As a request for APIs always requires authentication since there
is no session in APIs world, Devise counts +1 on every request since it
contains authentication info.

It happens because Devise has a trackable hook that updates the trackable
info everytime that the user is signed in by Warden.

* Fix
We are moving update_trackable_fields! from trackable hook (which was removed)
to sign_in_out and database_authenticatable. This way, update_trackable_fields!
is going to run only when the user signed in by Devise (only one time).
2019-09-19 10:22:29 -03:00
Felipe Renan
a1c493b009 Remove unnecessary checks for Rails 4 2019-09-19 10:22:29 -03:00
Felipe Renan
e7f9805fd4 Update CHANGELOG.md 2019-09-19 10:22:29 -03:00
Felipe Renan
8956d4caa1 Remove Ruby 2.2 and Ruby 2.1 support 2019-09-19 10:22:29 -03:00
Julius Graakjær Grantzau
b85911dee3 Downcase authentication keys and humanize error message (#4834) 2019-09-19 10:21:02 -03:00
Felipe Renan
2d1a961c1b Remove Rails 4 support 2019-09-19 10:20:59 -03:00
Adan Amarillas
195cbfb9e5 Modified sessions controller to return 401 for destroy action with no user signed in (#4878) 2019-09-19 10:06:14 -03:00
Leonardo Tegon
96a3153c23 Update CHANGELOG.md 2019-09-19 10:06:14 -03:00
Shriram
64238fc80e Make secure_compare handle empty strings comparison correctly
Used Rails' secure_compare method inside the definition of secure_compare. This will handle the empty strings comparison and return true when both the parameters are blank strings.

Fixes #4441
2019-09-19 10:06:14 -03:00
Siva Gollapalli
afaad713ff Added translations according to unlock strategy 2019-09-19 10:06:13 -03:00
57 changed files with 235 additions and 314 deletions

View File

@@ -1,8 +1,6 @@
language: ruby
rvm:
- 2.1.10
- 2.2.10
- 2.3.8
- 2.4.5
- 2.5.3
@@ -14,41 +12,14 @@ gemfile:
- gemfiles/Gemfile.rails-6.0-stable
- gemfiles/Gemfile.rails-5.2-stable
- gemfiles/Gemfile.rails-5.0-stable
- gemfiles/Gemfile.rails-4.2-stable
- gemfiles/Gemfile.rails-4.1-stable
matrix:
exclude:
- rvm: 2.1.10
gemfile: Gemfile
- rvm: 2.1.10
gemfile: gemfiles/Gemfile.rails-6.0-stable
- rvm: 2.1.10
gemfile: gemfiles/Gemfile.rails-5.2-stable
- rvm: 2.1.10
gemfile: gemfiles/Gemfile.rails-5.0-stable
- rvm: 2.2.10
gemfile: Gemfile
- rvm: 2.2.10
gemfile: gemfiles/Gemfile.rails-6.0-stable
- rvm: 2.2.10
gemfile: gemfiles/Gemfile.rails-5.2-stable
- rvm: 2.3.8
gemfile: gemfiles/Gemfile.rails-6.0-stable
- rvm: 2.4.5
gemfile: gemfiles/Gemfile.rails-4.1-stable
- rvm: 2.4.5
gemfile: gemfiles/Gemfile.rails-6.0-stable
- rvm: 2.5.3
gemfile: gemfiles/Gemfile.rails-4.1-stable
- rvm: 2.6.0
gemfile: gemfiles/Gemfile.rails-4.1-stable
- rvm: 2.6.0
gemfile: gemfiles/Gemfile.rails-4.2-stable
- rvm: ruby-head
gemfile: gemfiles/Gemfile.rails-4.1-stable
- rvm: ruby-head
gemfile: gemfiles/Gemfile.rails-4.2-stable
- env: DEVISE_ORM=mongoid
gemfile: Gemfile
- env: DEVISE_ORM=mongoid
@@ -71,10 +42,7 @@ env:
- DEVISE_ORM=active_record
- DEVISE_ORM=mongoid
before_install:
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
- gem install bundler -v '< 2'
- "rm ${BUNDLE_GEMFILE}.lock"
before_install: "rm ${BUNDLE_GEMFILE}.lock"
before_script: "bundle update"

View File

@@ -1,3 +1,13 @@
### 5.0.0-rc
* enhancements
* Suport multiple translations according to unlock strategy (by @sivagollapalli)
* Use `ActiveSupport::SecurityUtils.secure_compare` inside `Devise.secure_compare` (by @shrirambalakrishnan)
* Update trackable fields only in a database sign in (by @waiting-for-dev)
* deprecations
* Remove Rails 4, Ruby 2.1 and Ruby 2.2 support (by @feliperenan)
### Unreleased
* enhancements
* Increase default stretches to 12 (by @sergey-alekseev)

View File

@@ -13,7 +13,7 @@ PATH
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
railties (>= 5.0)
responders
warden (~> 1.2.3)

View File

@@ -137,17 +137,17 @@ Please note that the command output will show the variable value being used.
### BUNDLE_GEMFILE
We can use this variable to tell bundler what Gemfile it should use (instead of the one in the current directory).
Inside the [gemfiles](https://github.com/plataformatec/devise/tree/master/gemfiles) directory, we have one for each version of Rails we support. When you send us a pull request, it may happen that the test suite breaks on Travis using some of them. If that's the case, you can simulate the same environment using the `BUNDLE_GEMFILE` variable.
For example, if the tests broke using Ruby 2.4.2 and Rails 4.1, you can do the following:
For example, if the tests broke using Ruby 2.5.0 and Rails 5.0, you can do the following:
```bash
rbenv shell 2.4.2 # or rvm use 2.4.2
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-4.1-stable bundle install
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-4.1-stable bin/test
rbenv shell 2.5.0 # or rvm use 2.5.0
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-5.0-stable bundle install
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-5.0-stable bin/test
```
You can also combine both of them if the tests broke for Mongoid:
```bash
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-4.1-stable bundle install
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-4.1-stable DEVISE_ORM=mongoid bin/test
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-5.0-stable bundle install
BUNDLE_GEMFILE=gemfiles/Gemfile.rails-5.0-stable DEVISE_ORM=mongoid bin/test
```
### Running tests
@@ -180,7 +180,7 @@ Once you have solidified your understanding of Rails and authentication mechanis
## Getting started
Devise 4.0 works with Rails 4.1 onwards. Add the following line to your Gemfile:
Devise 5.0 works with Rails 5.0 onwards. Add the following line to your Gemfile:
```ruby
gem 'devise'

View File

@@ -47,7 +47,12 @@ class Devise::PasswordsController < DeviseController
respond_with resource, location: after_resetting_password_path_for(resource)
else
set_minimum_password_length
respond_with resource
if expired_token_error?(resource)
redirect_to new_password_path(resource_name), alert: t('devise.passwords.expired_token')
else
respond_with resource
end
end
end
@@ -80,4 +85,9 @@ class Devise::PasswordsController < DeviseController
def translation_scope
'devise.passwords'
end
private
def expired_token_error?(resource)
resource.errors.details[:reset_password_token].any? { |error| error[:error] == :expired }
end
end

View File

@@ -28,7 +28,7 @@ class Devise::SessionsController < DeviseController
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message! :notice, :signed_out if signed_out
yield if block_given?
respond_to_on_destroy
respond_to_on_destroy(status: :no_content)
end
protected
@@ -62,7 +62,7 @@ class Devise::SessionsController < DeviseController
if all_signed_out?
set_flash_message! :notice, :already_signed_out
respond_to_on_destroy
respond_to_on_destroy(status: :unauthorized)
end
end
@@ -72,11 +72,11 @@ class Devise::SessionsController < DeviseController
users.all?(&:blank?)
end
def respond_to_on_destroy
def respond_to_on_destroy(status:)
# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request
respond_to do |format|
format.all { head :no_content }
format.all { head status }
format.any(*navigational_formats) { redirect_to after_sign_out_path_for(resource_name) }
end
end

View File

@@ -10,7 +10,11 @@ en:
already_authenticated: "You are already signed in."
inactive: "Your account is not activated yet."
invalid: "Invalid %{authentication_keys} or password."
locked: "Your account is locked."
locked:
none: "Your account is locked."
email: "Your account is locked. An email has been sent with instructions on how to unlock your account."
time: "Your account is locked. Your account will become available after a certain amount of time."
both: "Your account is locked. An email has been sent with instructions on how to unlock your account, or wait a certain amount of time and try again."
last_attempt: "You have one more attempt before your account is locked."
not_found_in_database: "Invalid %{authentication_keys} or password."
timeout: "Your session expired. Please sign in again to continue."
@@ -36,6 +40,7 @@ en:
send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
updated: "Your password has been changed successfully. You are now signed in."
updated_not_active: "Your password has been changed successfully."
expired_token: "The password recovery link expired. Please request a new one."
registrations:
destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
signed_up: "Welcome! You have signed up successfully."

View File

@@ -17,11 +17,11 @@ Gem::Specification.new do |s|
s.files = Dir["{app,config,lib}/**/*", "CHANGELOG.md", "MIT-LICENSE", "README.md"]
s.require_paths = ["lib"]
s.required_ruby_version = '>= 2.1.0'
s.required_ruby_version = '>= 2.3.0'
s.add_dependency("warden", "~> 1.2.3")
s.add_dependency("orm_adapter", "~> 0.1")
s.add_dependency("bcrypt", "~> 3.0")
s.add_dependency("railties", ">= 4.1.0")
s.add_dependency("railties", ">= 5.0")
s.add_dependency("responders")
end

View File

@@ -21,7 +21,6 @@ group :test do
gem "timecop"
gem "webrat", "0.7.3", require: false
gem "mocha", "~> 1.1", require: false
gem 'test_after_commit', require: false
end
platforms :ruby do

View File

@@ -13,7 +13,7 @@ PATH
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
railties (>= 5.0)
responders
warden (~> 1.2.3)
@@ -153,8 +153,6 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
test_after_commit (1.1.0)
activerecord (>= 3.2)
thor (0.19.4)
thread_safe (0.3.6)
timecop (0.8.1)

View File

@@ -19,7 +19,6 @@ group :test do
gem "timecop"
gem "webrat", "0.7.3", require: false
gem "mocha", "~> 1.1", require: false
gem 'test_after_commit', require: false
end
platforms :ruby do

View File

@@ -13,7 +13,7 @@ PATH
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
railties (>= 5.0)
responders
warden (~> 1.2.3)
@@ -162,8 +162,6 @@ GEM
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sqlite3 (1.3.13)
test_after_commit (1.1.0)
activerecord (>= 3.2)
thor (0.20.0)
thread_safe (0.3.6)
timecop (0.9.1)

View File

@@ -13,7 +13,7 @@ PATH
devise (4.7.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
railties (>= 5.0)
responders
warden (~> 1.2.3)

View File

@@ -502,12 +502,8 @@ module Devise
# constant-time comparison algorithm to prevent timing attacks
def self.secure_compare(a, b)
return false if a.blank? || b.blank? || a.bytesize != b.bytesize
l = a.unpack "C#{a.bytesize}"
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
return false if a.nil? || b.nil?
ActiveSupport::SecurityUtils.secure_compare(a, b)
end
end

View File

@@ -53,7 +53,7 @@ module Devise
def #{group_name}_signed_in?
#{mappings}.any? do |mapping|
warden.authenticate?(scope: mapping)
warden.authenticated?(scope: mapping)
end
end
@@ -119,7 +119,7 @@ module Devise
end
def #{mapping}_signed_in?
!!current_#{mapping}
!!(@current_#{mapping} || warden.authenticated?(scope: :#{mapping}))
end
def current_#{mapping}

View File

@@ -12,7 +12,7 @@ module Devise
# authentication hooks, you can directly call `warden.authenticated?(scope: scope)`
def signed_in?(scope=nil)
[scope || Devise.mappings.keys].flatten.any? do |_scope|
warden.authenticate?(scope: _scope)
warden.authenticated?(scope: _scope)
end
end
@@ -51,6 +51,7 @@ module Devise
true
else
warden.set_user(resource, options.merge!(scope: scope))
resource.update_tracked_fields!(warden.request) if resource.respond_to?(:update_tracked_fields!)
end
end

View File

@@ -103,11 +103,14 @@ module Devise
options[:scope] = "devise.failure"
options[:default] = [message]
auth_keys = scope_class.authentication_keys
keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) }
keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key).downcase }
options[:authentication_keys] = keys.join(I18n.translate(:"support.array.words_connector"))
options = i18n_options(options)
translated_message = I18n.t(:"#{scope}.#{message}", options)
I18n.t(:"#{scope}.#{message}", options)
# only call `#humanize` when the message is `:invalid` to ensure the original format
# of other messages - like `:does_not_exist` - is kept.
message == :invalid ? translated_message.humanize : translated_message
else
message.to_s
end

View File

@@ -1,11 +0,0 @@
# frozen_string_literal: true
# After each sign in, update sign in time, sign in count and sign in IP.
# This is only triggered when the user is explicitly set (with set_user)
# and on authentication. Retrieving the user from session (:fetch) does
# not trigger it.
Warden::Manager.after_set_user except: :fetch do |record, warden, options|
if record.respond_to?(:update_tracked_fields!) && warden.authenticated?(options[:scope]) && !warden.request.env['devise.skip_trackable']
record.update_tracked_fields!(warden.request)
end
end

View File

@@ -182,11 +182,8 @@ module Devise
# # Deliver later with Active Job's `deliver_later`
# if message.respond_to?(:deliver_later)
# message.deliver_later
# # Remove once we move to Rails 4.2+ only, as `deliver` is deprecated.
# elsif message.respond_to?(:deliver_now)
# message.deliver_now
# else
# message.deliver
# message.deliver_now
# end
# end
#
@@ -194,12 +191,7 @@ module Devise
#
def send_devise_notification(notification, *args)
message = devise_mailer.send(notification, self, *args)
# Remove once we move to Rails 4.2+ only.
if message.respond_to?(:deliver_now)
message.deliver_now
else
message.deliver
end
message.deliver_now
end
def downcase_keys

View File

@@ -80,16 +80,7 @@ module Devise
# users to change relevant information like the e-mail without changing
# their password). In case the password field is rejected, the confirmation
# is also rejected as long as it is also blank.
def update_with_password(params, *options)
if options.present?
ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
[Devise] The second argument of `DatabaseAuthenticatable#update_with_password`
(`options`) is deprecated and it will be removed in the next major version.
It was added to support a feature deprecated in Rails 4, so you can safely remove it
from your code.
DEPRECATION
end
def update_with_password(params)
current_password = params.delete(:current_password)
if params[:password].blank?
@@ -98,9 +89,9 @@ module Devise
end
result = if valid_password?(current_password)
update(params, *options)
update(params)
else
assign_attributes(params, *options)
assign_attributes(params)
valid?
errors.add(:current_password, current_password.blank? ? :blank : :invalid)
false
@@ -122,20 +113,11 @@ module Devise
# super(params)
# end
#
def update_without_password(params, *options)
if options.present?
ActiveSupport::Deprecation.warn <<-DEPRECATION.strip_heredoc
[Devise] The second argument of `DatabaseAuthenticatable#update_without_password`
(`options`) is deprecated and it will be removed in the next major version.
It was added to support a feature deprecated in Rails 4, so you can safely remove it
from your code.
DEPRECATION
end
def update_without_password(params)
params.delete(:password)
params.delete(:password_confirmation)
result = update(params, *options)
result = update(params)
clean_up_passwords
result
end

View File

@@ -122,7 +122,15 @@ module Devise
if Devise.paranoid
super
elsif access_locked? || (lock_strategy_enabled?(:failed_attempts) && attempts_exceeded?)
:locked
if unlock_strategy_enabled?(:both)
'locked.both'.to_sym
elsif unlock_strategy_enabled?(:email)
'locked.email'.to_sym
elsif unlock_strategy_enabled?(:time)
'locked.time'.to_sym
else
'locked.none'.to_sym
end
elsif lock_strategy_enabled?(:failed_attempts) && last_attempt? && self.class.last_attempt_warning
:last_attempt
else

View File

@@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'devise/hooks/trackable'
module Devise
module Models
# Track information about your user sign in. It tracks the following columns:
@@ -36,6 +34,8 @@ module Devise
# See https://github.com/plataformatec/devise/issues/4673 for more details.
return if new_record?
return if skip_trackable_and_not_active_for_authentication?(request)
update_tracked_fields(request)
save(validate: false)
end
@@ -46,6 +46,11 @@ module Devise
request.remote_ip
end
private
def skip_trackable_and_not_active_for_authentication?(request)
request.env['devise.skip_trackable'] || !active_for_authentication?
end
end
end
end

View File

@@ -130,8 +130,7 @@ module Devise
#
# Returns an +ActiveSupport::HashWithIndifferentAccess+.
def cast_to_hash(params)
# TODO: Remove the `with_indifferent_access` method call when we only support Rails 5+.
params && params.to_h.with_indifferent_access
params && params.to_h
end
def default_params

View File

@@ -12,6 +12,7 @@ module Devise
if validate(resource){ hashed = true; resource.valid_password?(password) }
remember_me(resource)
resource.update_tracked_fields!(request) if resource.respond_to?(:update_tracked_fields!)
resource.after_database_authentication
success!(resource)
end

View File

@@ -139,7 +139,6 @@ module Devise
status, headers, response = Devise.warden_config[:failure_app].call(env).to_a
@controller.response.headers.merge!(headers)
@controller.response.content_type = headers["Content-Type"] unless Rails::VERSION::MAJOR >= 5
@controller.status = status
@controller.response.body = response.body
nil # causes process return @response

View File

@@ -82,23 +82,17 @@ RUBY
postgresql?
end
def rails5_and_up?
Rails::VERSION::MAJOR >= 5
end
def postgresql?
config = ActiveRecord::Base.configurations[Rails.env]
config && config['adapter'] == 'postgresql'
end
def migration_version
if rails5_and_up?
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
end
def primary_key_type
primary_key_string if rails5_and_up?
primary_key_string
end
def primary_key_string

View File

@@ -37,10 +37,6 @@ module Devise
def show_readme
readme "README" if behavior == :invoke
end
def rails_4?
Rails::VERSION::MAJOR == 4
end
end
end
end

View File

@@ -15,21 +15,21 @@ class ControllerAuthenticatableTest < Devise::ControllerTestCase
assert_equal @mock_warden, @controller.warden
end
test 'proxy signed_in?(scope) to authenticate?' do
@mock_warden.expects(:authenticate?).with(scope: :my_scope)
test 'proxy signed_in?(scope) to authenticated?' do
@mock_warden.expects(:authenticated?).with(scope: :my_scope)
@controller.signed_in?(:my_scope)
end
test 'proxy signed_in?(nil) to authenticate?' do
test 'proxy signed_in?(nil) to authenticated?' do
Devise.mappings.keys.each do |scope| # :user, :admin, :manager
@mock_warden.expects(:authenticate?).with(scope: scope)
@mock_warden.expects(:authenticated?).with(scope: scope)
end
@controller.signed_in?
end
test 'proxy [group]_signed_in? to authenticate? with each scope' do
test 'proxy [group]_signed_in? to authenticated? with each scope' do
[:user, :admin].each do |scope|
@mock_warden.expects(:authenticate?).with(scope: scope).returns(false)
@mock_warden.expects(:authenticated?).with(scope: scope).returns(false)
end
@controller.commenter_signed_in?
end
@@ -81,7 +81,7 @@ class ControllerAuthenticatableTest < Devise::ControllerTestCase
test 'proxy authenticate_[group]! to authenticate!? with each scope' do
[:user, :admin].each do |scope|
@mock_warden.expects(:authenticate!).with(scope: scope)
@mock_warden.expects(:authenticate?).with(scope: scope).returns(false)
@mock_warden.expects(:authenticated?).with(scope: scope).returns(false)
end
@controller.authenticate_commenter!
end
@@ -91,18 +91,18 @@ class ControllerAuthenticatableTest < Devise::ControllerTestCase
@controller.authenticate_publisher_account!
end
test 'proxy user_signed_in? to authenticate with user scope' do
@mock_warden.expects(:authenticate).with(scope: :user).returns("user")
test 'proxy user_signed_in? to authenticated? with user scope' do
@mock_warden.expects(:authenticated?).with(scope: :user).returns("user")
assert @controller.user_signed_in?
end
test 'proxy admin_signed_in? to authenticatewith admin scope' do
@mock_warden.expects(:authenticate).with(scope: :admin)
test 'proxy admin_signed_in? to authenticated? with admin scope' do
@mock_warden.expects(:authenticated?).with(scope: :admin)
refute @controller.admin_signed_in?
end
test 'proxy publisher_account_signed_in? to authenticate with namespaced publisher account scope' do
@mock_warden.expects(:authenticate).with(scope: :publisher_account)
test 'proxy publisher_account_signed_in? to authenticated? with namespaced publisher account scope' do
@mock_warden.expects(:authenticated?).with(scope: :publisher_account)
@controller.publisher_account_signed_in?
end

View File

@@ -36,4 +36,10 @@ class PasswordsControllerTest < Devise::ControllerTestCase
User.any_instance.expects :after_database_authentication
put_update_with_params
end
test 'redirects to new_password_path when token has expired' do
@user.update(reset_password_sent_at: Time.now - 1.year)
put_update_with_params
assert_redirected_to new_user_password_path
end
end

View File

@@ -74,7 +74,7 @@ class SessionsControllerTest < Devise::ControllerTestCase
assert_template "devise/sessions/new"
end
test "#destroy doesn't set the flash if the requested format is not navigational" do
test "#destroy doesn't set the flash and returns 204 status if the requested format is not navigational" do
request.env["devise.mapping"] = Devise.mappings[:user]
user = create_user
user.confirm
@@ -88,6 +88,17 @@ class SessionsControllerTest < Devise::ControllerTestCase
assert_equal 204, @response.status
end
test "#destroy returns 401 status if user is not signed in and the requested format is not navigational" do
delete :destroy, format: 'json'
assert_equal 401, @response.status
end
test "#destroy returns 302 status if user is not signed in and the requested format is navigational" do
request.env["devise.mapping"] = Devise.mappings[:user]
delete :destroy
assert_equal 302, @response.status
end
if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:mass_assignment_sanitizer)
test "#new doesn't raise mass-assignment exception even if sign-in key is attr_protected" do
request.env["devise.mapping"] = Devise.mappings[:user]

View File

@@ -90,11 +90,14 @@ class DeviseTest < ActiveSupport::TestCase
[nil, ""].each do |empty|
refute Devise.secure_compare(empty, "something")
refute Devise.secure_compare("something", empty)
refute Devise.secure_compare(empty, empty)
end
refute Devise.secure_compare("size_1", "size_four")
end
test 'Devise.secure_compare should return true if strings are same' do
assert Devise.secure_compare('', '')
end
test 'Devise.email_regexp should match valid email addresses' do
valid_emails = ["test@example.com", "jo@jo.co", "f4$_m@you.com", "testing.example@example.com.ua", "test@tt", "test@valid---domain.com"]
non_valid_emails = ["rex", "test user@example.com", "test_user@example server.com"]

View File

@@ -185,17 +185,27 @@ class FailureTest < ActiveSupport::TestCase
test 'uses the proxy failure message as symbol' do
call_failure('warden' => OpenStruct.new(message: :invalid))
assert_equal 'Invalid Email or password.', @request.flash[:alert]
assert_equal 'Invalid email or password.', @request.flash[:alert]
assert_equal 'http://test.host/users/sign_in', @response.second["Location"]
end
test 'supports authentication_keys as a Hash for the flash message' do
swap Devise, authentication_keys: { email: true, login: true } do
call_failure('warden' => OpenStruct.new(message: :invalid))
assert_equal 'Invalid Email, Login or password.', @request.flash[:alert]
assert_equal 'Invalid email, login or password.', @request.flash[:alert]
end
end
test 'downcases authentication_keys for the flash message' do
call_failure('warden' => OpenStruct.new(message: :invalid))
assert_equal 'Invalid email or password.', @request.flash[:alert]
end
test 'humanizes the flash message' do
call_failure('warden' => OpenStruct.new(message: :invalid))
assert_equal @request.flash[:alert], @request.flash[:alert].humanize
end
test 'uses custom i18n options' do
call_failure('warden' => OpenStruct.new(message: :does_not_exist), app: FailureWithI18nOptions)
assert_equal 'User Steve does not exist', @request.flash[:alert]
@@ -278,7 +288,7 @@ class FailureTest < ActiveSupport::TestCase
test 'uses the failure message as response body' do
call_failure('formats' => Mime[:xml], 'warden' => OpenStruct.new(message: :invalid))
assert_match '<error>Invalid Email or password.</error>', @response.third.body
assert_match '<error>Invalid email or password.</error>', @response.third.body
end
context 'on ajax call' do
@@ -327,7 +337,7 @@ class FailureTest < ActiveSupport::TestCase
}
call_failure(env)
assert @response.third.body.include?('<h2>Log in</h2>')
assert @response.third.body.include?('Invalid Email or password.')
assert @response.third.body.include?('Invalid email or password.')
end
test 'calls the original controller if not confirmed email' do
@@ -362,7 +372,7 @@ class FailureTest < ActiveSupport::TestCase
}
call_failure(env)
assert @response.third.body.include?('<h2>Log in</h2>')
assert @response.third.body.include?('Invalid Email or password.')
assert @response.third.body.include?('Invalid email or password.')
assert_equal @request.env["SCRIPT_NAME"], '/sample'
assert_equal @request.env["PATH_INFO"], '/users/sign_in'
end

View File

@@ -48,7 +48,6 @@ if DEVISE_ORM == :active_record
run_generator %w(monster)
assert_file "app/models/monster.rb"
run_generator %w(monster)
if Rails.version >= '5.0.3'
assert_migration "db2/migrate/add_devise_to_monsters.rb"
else
@@ -84,11 +83,7 @@ if DEVISE_ORM == :active_record
test "add primary key type with rails 5 when specified in rails generator" do
run_generator ["monster", "--primary_key_type=uuid"]
if Devise::Test.rails5_and_up?
assert_migration "db/migrate/devise_create_monsters.rb", /create_table :monsters, id: :uuid do/
else
assert_migration "db/migrate/devise_create_monsters.rb", /create_table :monsters do/
end
assert_migration "db/migrate/devise_create_monsters.rb", /create_table :monsters, id: :uuid do/
end
end

View File

@@ -557,7 +557,7 @@ class AuthenticationKeysTest < Devise::IntegrationTest
test 'missing authentication keys cause authentication to abort' do
swap Devise, authentication_keys: [:subdomain] do
sign_in_as_user
assert_contain "Invalid Subdomain or password."
assert_contain "Invalid subdomain or password."
refute warden.authenticated?(:user)
end
end
@@ -596,7 +596,7 @@ class AuthenticationRequestKeysTest < Devise::IntegrationTest
swap Devise, request_keys: [:subdomain] do
sign_in_as_user
assert_contain "Invalid Email or password."
assert_contain "Invalid email or password."
refute warden.authenticated?(:user)
end
end

View File

@@ -142,7 +142,7 @@ class ConfirmationTest < Devise::IntegrationTest
fill_in 'password', with: 'invalid'
end
assert_contain 'Invalid Email or password'
assert_contain 'Invalid email or password'
refute warden.authenticated?(:user)
end
end

View File

@@ -70,7 +70,7 @@ class DatabaseAuthenticationTest < Devise::IntegrationTest
fill_in 'password', with: 'abcdef'
end
assert_contain 'Invalid Email or password'
assert_contain 'Invalid email or password'
refute warden.authenticated?(:admin)
end
@@ -82,7 +82,7 @@ class DatabaseAuthenticationTest < Devise::IntegrationTest
end
assert_not_contain 'Not found in database'
assert_contain 'Invalid Email or password.'
assert_contain 'Invalid email or password.'
end
end
end

View File

@@ -52,7 +52,7 @@ class HttpAuthenticationTest < Devise::IntegrationTest
sign_in_as_new_user_with_http("unknown")
assert_equal 401, status
assert_equal "application/xml; charset=utf-8", headers["Content-Type"]
assert_match "<error>Invalid Email or password.</error>", response.body
assert_match "<error>Invalid email or password.</error>", response.body
end
test 'returns a custom response with www-authenticate and chosen realm' do

View File

@@ -104,7 +104,7 @@ class LockTest < Devise::IntegrationTest
test 'error message is configurable by resource name' do
store_translations :en, devise: {
failure: {user: {locked: "You are locked!"}}
failure: {user: {locked: { both: "You are locked!" }}}
} do
user = create_user(locked: true)
@@ -118,7 +118,7 @@ class LockTest < Devise::IntegrationTest
test "user should not be able to sign in when locked" do
store_translations :en, devise: {
failure: {user: {locked: "You are locked!"}}
failure: {user: {locked: {both: "You are locked!"}}}
} do
user = create_user(locked: true)

View File

@@ -152,6 +152,19 @@ class PasswordTest < Devise::IntegrationTest
refute user.reload.valid_password?('987654321')
end
test 'not authenticated user with expired reset password token should be redirected to new password path' do
user = create_user
request_forgot_password
user.update(reset_password_sent_at: Time.now - 1.year)
visit edit_user_password_path(reset_password_token: 'abcdef')
fill_in 'New password', with: '987654321'
fill_in 'Confirm new password', with: '987654321'
click_button 'Change my password'
assert_contain 'The password recovery link expired. Please request a new one.'
end
test 'not authenticated user with valid reset password token but invalid password should not be able to change their password' do
user = create_user
request_forgot_password

View File

@@ -14,10 +14,8 @@ class RememberMeTest < Devise::IntegrationTest
def generate_signed_cookie(raw_cookie)
request = if Devise::Test.rails51? || Devise::Test.rails52_and_up?
ActionController::TestRequest.create(Class.new) # needs a "controller class"
elsif Devise::Test.rails5?
ActionController::TestRequest.create
else
ActionController::TestRequest.new
ActionController::TestRequest.create
end
request.cookie_jar.signed['raw_cookie'] = raw_cookie
request.cookie_jar['raw_cookie']

View File

@@ -95,5 +95,4 @@ class TrackableHooksTest < Devise::IntegrationTest
user.reload
assert_equal 1, user.sign_in_count
end
end

View File

@@ -312,7 +312,7 @@ class LockableTest < ActiveSupport::TestCase
end
test 'should return last attempt message if user made next-to-last attempt of password entering' do
swap Devise, last_attempt_warning: true, lock_strategy: :failed_attempts do
swap Devise, last_attempt_warning: true, lock_strategy: :failed_attempts, unlock_strategy: :none do
user = create_user
user.failed_attempts = Devise.maximum_attempts - 2
assert_equal :invalid, user.unauthenticated_message
@@ -321,7 +321,7 @@ class LockableTest < ActiveSupport::TestCase
assert_equal :last_attempt, user.unauthenticated_message
user.failed_attempts = Devise.maximum_attempts
assert_equal :locked, user.unauthenticated_message
assert_equal :'locked.none', user.unauthenticated_message
end
end
@@ -336,7 +336,22 @@ class LockableTest < ActiveSupport::TestCase
test 'should return locked message if user was programatically locked' do
user = create_user
user.lock_access!
assert_equal :locked, user.unauthenticated_message
swap Devise, unlock_strategy: :none do
assert_equal :'locked.none', user.unauthenticated_message
end
swap Devise, unlock_strategy: :both do
assert_equal :'locked.both', user.unauthenticated_message
end
swap Devise, unlock_strategy: :email do
assert_equal :'locked.email', user.unauthenticated_message
end
swap Devise, unlock_strategy: :time do
assert_equal :'locked.time', user.unauthenticated_message
end
end
test 'unlock_strategy_enabled? should return true for both, email, and time strategies if :both is used' do

View File

@@ -60,6 +60,39 @@ class TrackableTest < ActiveSupport::TestCase
assert_not user.update_tracked_fields!(request)
end
test "update_tracked_fields! runs when isn't a new record and the validations are ok" do
user = create_user
user.stubs(:active_for_authentication?).returns(true)
request = mock
request.stubs(:remote_ip).returns("127.0.0.1")
request.stubs(:env).returns('devise.skip_trackable' => nil)
assert user.update_tracked_fields!(request)
end
test "update_tracked_fields! should not run when skip trackable is turned on" do
user = create_user
user.stubs(:active_for_authentication?).returns(true)
request = mock
request.stubs(:remote_ip).returns("127.0.0.1")
request.stubs(:env).returns('devise.skip_trackable' => 1)
assert_not user.update_tracked_fields!(request)
end
test "update_tracked_fields! should not run when the user is not active for authentication" do
user = create_user
user.stubs(:active_for_authentication?).returns(false)
request = mock
request.stubs(:remote_ip).returns("127.0.0.1")
request.stubs(:env).returns('devise.skip_trackable' => nil)
assert_not user.update_tracked_fields!(request)
end
test 'extract_ip_from should be overridable' do
class UserWithOverride < User
protected

View File

@@ -14,13 +14,7 @@ else
end
class ActiveSupport::TestCase
if Devise::Test.rails5_and_up?
self.use_transactional_tests = true
else
# Let `after_commit` work with transactional fixtures, however this is not needed for Rails 5.
require 'test_after_commit'
self.use_transactional_fixtures = true
end
self.use_transactional_tests = true
self.use_instantiated_fixtures = false
end

View File

@@ -5,7 +5,7 @@ require 'shared_user'
class User < ActiveRecord::Base
include Shim
include SharedUser
include ActiveModel::Serializers::Xml if Devise::Test.rails5_and_up?
include ActiveModel::Serializers::Xml
validates :sign_in_count, presence: true

View File

@@ -22,10 +22,6 @@ class HomeController < ApplicationController
end
def unauthenticated
if Devise::Test.rails5_and_up?
render body: "unauthenticated", status: :unauthorized
else
render text: "unauthenticated", status: :unauthorized
end
render body: "unauthenticated", status: :unauthorized
end
end

View File

@@ -11,6 +11,6 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
user = User.to_adapter.find_first(email: 'user@test.com')
user.remember_me = true
sign_in user
render (Devise::Test.rails5_and_up? ? :body : :text) => ""
render body: ""
end
end

View File

@@ -15,7 +15,7 @@ class UsersController < ApplicationController
end
def update_form
render (Devise::Test.rails5_and_up? ? :body : :text) => 'Update'
render body: 'Update'
end
def accept
@@ -23,11 +23,11 @@ class UsersController < ApplicationController
end
def exhibit
render (Devise::Test.rails5_and_up? ? :body : :text) => current_user ? "User is authenticated" : "User is not authenticated"
render body: current_user ? "User is authenticated" : "User is not authenticated"
end
def expire
user_session['last_request_at'] = 31.minutes.ago.utc
render (Devise::Test.rails5_and_up? ? :body : :text) => 'User will be expired on next request'
render body: 'User will be expired on next request'
end
end

View File

@@ -33,12 +33,6 @@ module RailsApp
# config.assets.enabled = false
config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
rails_version = Gem::Version.new(Rails.version)
if DEVISE_ORM == :active_record &&
rails_version >= Gem::Version.new('4.2.0') &&
rails_version < Gem::Version.new('5.1.0')
config.active_record.raise_in_transactional_callbacks = true
end
# This was used to break devise in some situations
config.to_prepare do

View File

@@ -7,7 +7,6 @@ end
module Devise
module Test
# Detection for minor differences between Rails versions in tests.
def self.rails6?
Rails.version.start_with? '6'
end
@@ -23,14 +22,6 @@ module Devise
def self.rails51?
Rails.version.start_with? '5.1'
end
def self.rails5_and_up?
Rails::VERSION::MAJOR >= 5
end
def self.rails5?
Rails.version.start_with? '5'
end
end
end

View File

@@ -22,13 +22,7 @@ RailsApp::Application.configure do
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
if Devise::Test.rails5_and_up?
config.public_file_server.enabled = false
elsif Rails.version >= "4.2.0"
config.serve_static_files = false
else
config.serve_static_assets = false
end
config.public_file_server.enabled = false
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier

View File

@@ -16,16 +16,8 @@ RailsApp::Application.configure do
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
if Devise::Test.rails5_and_up?
config.public_file_server.enabled = true
config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
elsif Rails.version >= "4.2.0"
config.serve_static_files = true
config.static_cache_control = "public, max-age=3600"
else
config.serve_static_assets = true
config.static_cache_control = "public, max-age=3600"
end
config.public_file_server.enabled = true
config.public_file_server.headers = {'Cache-Control' => 'public, max-age=3600'}
# Show full error reports and disable caching.
config.consider_all_requests_local = true

View File

@@ -1,10 +1,6 @@
# frozen_string_literal: true
superclass = ActiveRecord::Migration
# TODO: Inherit from the 5.0 Migration class directly when we drop support for Rails 4.
superclass = ActiveRecord::Migration[5.0] if superclass.respond_to?(:[])
class CreateTables < superclass
class CreateTables < ActiveRecord::Migration[5.0]
def self.up
create_table :users do |t|
t.string :username

View File

@@ -204,8 +204,7 @@ class CustomizedRoutingTest < ActionController::TestCase
end
test 'map with format false for sessions' do
expected_params = {controller: 'devise/sessions', action: 'new'}
expected_params[:format] = false if Devise::Test.rails5_and_up?
expected_params = {controller: 'devise/sessions', action: 'new', format: false}
assert_recognizes(expected_params, {path: '/htmlonly_admin/sign_in', method: :get})
assert_raise ExpectedRoutingError do
@@ -214,8 +213,7 @@ class CustomizedRoutingTest < ActionController::TestCase
end
test 'map with format false for passwords' do
expected_params = {controller: 'devise/passwords', action: 'create'}
expected_params[:format] = false if Devise::Test.rails5_and_up?
expected_params = {controller: 'devise/passwords', action: 'create', format: false}
assert_recognizes(expected_params, {path: '/htmlonly_admin/password', method: :post})
assert_raise ExpectedRoutingError do
@@ -224,8 +222,7 @@ class CustomizedRoutingTest < ActionController::TestCase
end
test 'map with format false for registrations' do
expected_params = {controller: 'devise/registrations', action: 'new'}
expected_params[:format] = false if Devise::Test.rails5_and_up?
expected_params = {controller: 'devise/registrations', action: 'new', format: false}
assert_recognizes(expected_params, {path: '/htmlonly_admin/sign_up', method: :get})
assert_raise ExpectedRoutingError do
@@ -234,8 +231,7 @@ class CustomizedRoutingTest < ActionController::TestCase
end
test 'map with format false for confirmations' do
expected_params = {controller: 'devise/confirmations', action: 'show'}
expected_params[:format] = false if Devise::Test.rails5_and_up?
expected_params = {controller: 'devise/confirmations', action: 'show', format: false}
assert_recognizes(expected_params, {path: '/htmlonly_users/confirmation', method: :get})
assert_raise ExpectedRoutingError do
@@ -244,8 +240,7 @@ class CustomizedRoutingTest < ActionController::TestCase
end
test 'map with format false for unlocks' do
expected_params = {controller: 'devise/unlocks', action: 'show'}
expected_params[:format] = false if Devise::Test.rails5_and_up?
expected_params = {controller: 'devise/unlocks', action: 'show', format: false}
assert_recognizes(expected_params, {path: '/htmlonly_users/unlock', method: :get})
assert_raise ExpectedRoutingError do

View File

@@ -44,38 +44,12 @@ class Rails52SecretKeyBase
def config
OpenStruct.new(secret_key_base: nil)
end
def secret_key_base
'secret_key_base'
end
end
class Rails41Secrets
def secrets
OpenStruct.new(secret_key_base: 'secrets')
end
def config
OpenStruct.new(secret_key_base: nil)
end
end
class Rails41Config
def secrets
OpenStruct.new(secret_key_base: nil)
end
def config
OpenStruct.new(secret_key_base: 'config')
end
end
class Rails40Config
def config
OpenStruct.new(secret_key_base: 'config')
end
end
class SecretKeyFinderTest < ActiveSupport::TestCase
test "rails 5.2 uses credentials when they're available" do
secret_key_finder = Devise::SecretKeyFinder.new(Rails52Credentials.new)
@@ -100,22 +74,4 @@ class SecretKeyFinderTest < ActiveSupport::TestCase
assert_equal 'secret_key_base', secret_key_finder.find
end
test "rails 4.1 uses secrets" do
secret_key_finder = Devise::SecretKeyFinder.new(Rails41Secrets.new)
assert_equal 'secrets', secret_key_finder.find
end
test "rails 4.1 uses config when secrets are empty" do
secret_key_finder = Devise::SecretKeyFinder.new(Rails41Config.new)
assert_equal 'config', secret_key_finder.find
end
test "rails 4.0 uses config" do
secret_key_finder = Devise::SecretKeyFinder.new(Rails40Config.new)
assert_equal 'config', secret_key_finder.find
end
end

View File

@@ -6,21 +6,11 @@ module Devise
# xhr get_via_redirect post_via_redirect
# ).each do |method|
%w( get post put ).each do |method|
if Devise::Test.rails5_and_up?
define_method(method) do |url, options={}|
if options.empty?
super url
else
super url, options
end
end
else
define_method(method) do |url, options={}|
if options[:xhr]==true
xml_http_request __method__, url, options[:params] || {}, options[:headers]
else
super url, options[:params] || {}, options[:headers]
end
define_method(method) do |url, options={}|
if options.empty?
super url
else
super url, options
end
end
end
@@ -31,21 +21,11 @@ module Devise
# xhr get_via_redirect post_via_redirect
# ).each do |method|
%w( get post put ).each do |method|
if Devise::Test.rails5_and_up?
define_method(method) do |action, options={}|
if options.empty?
super action
else
super action, options
end
end
else
define_method(method) do |action, options={}|
if options[:xhr]==true
xml_http_request __method__, action, options[:params] || {}, options[:headers]
else
super action, options[:params] || {}, options[:headers]
end
define_method(method) do |action, options={}|
if options.empty?
super action
else
super action, options
end
end
end

View File

@@ -178,10 +178,8 @@ class TestControllerHelpersTest < Devise::ControllerTestCase
@request = if Devise::Test.rails51? || Devise::Test.rails52_and_up?
ActionController::TestRequest.create(Class.new) # needs a "controller class"
elsif Devise::Test.rails5?
ActionController::TestRequest.create
else
ActionController::TestRequest.new
ActionController::TestRequest.create
end
new_warden_proxy = warden