diff --git a/lib/devise/models/validatable.rb b/lib/devise/models/validatable.rb index 62486cfbe0..3455287e9f 100644 --- a/lib/devise/models/validatable.rb +++ b/lib/devise/models/validatable.rb @@ -36,7 +36,7 @@ def self.included(base) validates_presence_of :password, if: :password_required? validates_confirmation_of :password, if: :password_required? - validates_length_of :password, minimum: proc { password_length.min }, maximum: proc { password_length.max }, allow_blank: true + validates_length_of :password, minimum: proc { password_length.min }, maximum: proc { password_length.max }, allow_blank: true, if: -> (x) { x.respond_to?(:password) } end end @@ -55,7 +55,7 @@ def self.assert_validations_api!(base) #:nodoc: # Passwords are always required if it's a new record, or if the password # or confirmation are being set somewhere. def password_required? - !persisted? || !password.nil? || !password_confirmation.nil? + respond_to?(:password) && (!persisted? || !password.nil? || !password_confirmation.nil?) end def email_required? diff --git a/test/models/validatable_test.rb b/test/models/validatable_test.rb index e8858de7e3..992e276aa3 100644 --- a/test/models/validatable_test.rb +++ b/test/models/validatable_test.rb @@ -121,4 +121,9 @@ class ValidatableTest < ActiveSupport::TestCase test 'required_fields should be an empty array' do assert_equal [], Devise::Models::Validatable.required_fields(User) end + + test 'should not fail is user has no password' do + user = create_user_without_password.reload + assert user.valid? + end end diff --git a/test/rails_app/app/active_record/user_without_password.rb b/test/rails_app/app/active_record/user_without_password.rb new file mode 100644 index 0000000000..fabb01000f --- /dev/null +++ b/test/rails_app/app/active_record/user_without_password.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "shared_user_without_password" + +class UserWithoutPassword < ActiveRecord::Base + self.table_name = 'users' + include Shim + include SharedUserWithoutPassword +end diff --git a/test/rails_app/app/mongoid/user_without_password.rb b/test/rails_app/app/mongoid/user_without_password.rb new file mode 100644 index 0000000000..b82ea56ad2 --- /dev/null +++ b/test/rails_app/app/mongoid/user_without_password.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "shared_user_without_email" + +class UserWithoutEmail + include Mongoid::Document + include Shim + include SharedUserWithoutEmail + + field :username, type: String + field :facebook_token, type: String + + ## Database authenticatable + field :email, type: String, default: "" + field :encrypted_password, type: String, default: "" + + ## Recoverable + field :reset_password_token, type: String + field :reset_password_sent_at, type: Time + + ## Rememberable + field :remember_created_at, type: Time + + ## Trackable + field :sign_in_count, type: Integer, default: 0 + field :current_sign_in_at, type: Time + field :last_sign_in_at, type: Time + field :current_sign_in_ip, type: String + field :last_sign_in_ip, type: String + + ## Lockable + field :failed_attempts, type: Integer, default: 0 # Only if lock strategy is :failed_attempts + field :unlock_token, type: String # Only if unlock strategy is :email or :both + field :locked_at, type: Time +end diff --git a/test/rails_app/config/routes.rb b/test/rails_app/config/routes.rb index 0b748f3fd7..ae7501c1e3 100644 --- a/test/rails_app/config/routes.rb +++ b/test/rails_app/config/routes.rb @@ -37,6 +37,11 @@ router_name: :main_app, module: :devise + devise_for :user_without_password, + class_name: 'UserWithoutPassword', + router_name: :main_app, + module: :devise + as :user do get "/as/sign_in", to: "devise/sessions#new" end diff --git a/test/rails_app/lib/shared_user_without_password.rb b/test/rails_app/lib/shared_user_without_password.rb new file mode 100644 index 0000000000..be623f94c6 --- /dev/null +++ b/test/rails_app/lib/shared_user_without_password.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SharedUserWithoutPassword + extend ActiveSupport::Concern + + included do + # NOTE: This is missing :database_authenticatable to avoid defining a `password` method. + # It is also missing :omniauthable because that adds unnecessary complexity to the setup + devise :confirmable, :lockable, :recoverable, + :registerable, :rememberable, :timeoutable, + :trackable, :validatable, + reconfirmable: false + + end +end diff --git a/test/support/helpers.rb b/test/support/helpers.rb index 01dc6aa562..fef12a5493 100644 --- a/test/support/helpers.rb +++ b/test/support/helpers.rb @@ -50,6 +50,10 @@ def create_user_without_email(attributes = {}) UserWithoutEmail.create!(valid_attributes(attributes)) end + def create_user_without_password(attributes = {}) + UserWithoutPassword.create!(valid_attributes(attributes).except(:password, :password_confirmation)) + end + def create_user_with_validations(attributes = {}) UserWithValidations.create!(valid_attributes(attributes)) end