I've looking for a way to allow users to change their settings (User model) without having to change their password (they still have to enter their current password). Devise out of the box seems to allow this, but when you remove the validatable module and setup custom validations, it seems you need to work around a bit.
I have setup the following validation in my user model :
validates :password, length: { in: 6..128 }
When signing up, user is required to specify his password (which is what I expect). When updating settings, though, if I leave password blank it raises an error to the user that says password must be at least 6 characters.
How can I work around this without having to change the way Devise works or having to implement a custom controller of some sort ?
Maybe this will look obvious for some, but it took me a while getting this together. After a few hours of trying different solutions and workarounds and looking all over the place, I dove deeper in Rails validations and found a few constructs that, when put together, make this really easy.
All I had to do was setup a validation for the create action and one for the update action and allow blanks on update.
validates :password, length: { in: 6..128 }, on: :create
validates :password, length: { in: 6..128 }, on: :update, allow_blank: true
With that, I'm getting the behaviour I want and it's only two short lines of code.
Additional note :
At first, I had tried this way :
validates :password, length: { in: 6..128 }, on: :create
This is wrong because it would skip the validation entirely on updates. Users would then be able to set short/long (or blank?) passwords when updating settings.
I tried Amal Kumar S solution, but unfortunately it didn't help me to solve the same issue, so here is modified version of solution tested in real project.
Here is the code from devise/models/validatable.rb module
protected
# Checks whether a password is needed or not. For validations only.
# 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?
end
The validation fails when you leave blank password and password confirmation fields while updating user info, because this condition is always false:
!password.nil? || !password_confirmation.nil?
Password and password_confirmation fields equals blank string '' which is never equals to nil. So you can fix that by overriding password_required? method in your user model and change nil? check to blank? check.
protected
def password_required?
!persisted? || !password.blank? || !password_confirmation.blank?
end
Think that it's easiest way to fix that and it's doesn't ruin original logic. Maybe it's devise's bug.
Add this code to your user model.
private
def password_required?
new_record? ? super : false
end
Devise has its own method to achieve that, update_without_password, It updates record attributes without asking for the current password. Never allows a change to the current password.
Note. If you are using this method, you should probably override this method to protect other attributes you would not like to be updated without a password.
Example:
def update_without_password(params={})
params.delete(:email)
super(params)
end
It is all in the Devise Documentation. http://rdoc.info/gems/devise/index
Related
If it's seen that a user is trying to log in with Facebook, I'd like to not require a password when logging in. However, the Authlogic (obviously) checks for a password when logging in. How can I disable password validation in this case?
Below is a short code snippet of the code that I'm working with. login_params changes depending on the method by which the user is logging in -- either by form fields or by OmniAuth.
login_params = if facebook_login?
if homeowner = get_facebook_user
{
email: homeowner.email,
password: homeowner.password
}
end
else
user_session_params
end
You could use a lambda in the validation to run it conditionally. For example:
validates :password, presence: true, if: lambda { !isNotFacebookLogin? }
In our application we have normal users. However, we want to be able to make invitations, to invite certain people. Note that an invitation is directly coupled to a user, as we want to be able to set certain settings for these users already. (We are mitigating clients from our old software to the new).
So:
An admin should be able to create a new user and change its settings.
When someone follows a link with their invitation_token, they should see a form where they can set a password for their account.
What I am having trouble with, is how to enable the admin to create an user account, bypassing the normal password validation. It would be a horrible solution if a default password would need to be set, as this would create a severe security flaw.
How to create a new User in Devise without providing a password?
There are at least two ways to do what you want:
Method 1:
Overload Devise's password_required? method
class User < ActiveRecord::Base
attr_accessor :skip_password_validation # virtual attribute to skip password validation while saving
protected
def password_required?
return false if skip_password_validation
super
end
end
Usage:
#user.skip_password_validation = true
#user.save
Method 2:
Disable validation with validate: false option:
user.save(validate: false)
This will skip validation of all fields (not only password). In this case you should make sure that all other fields are valid.
...
But I advise you to not create users without password in your particular case. I would create some additional table (for example, invitations) and store all required information including the fields that you want to be assigned to a user after confirmation.
TL;DR:
user.define_singleton_method(:password_required?) { false }
Fiddle:
class MockDeviseUser
protected
def password_required?
true
end
end
class User < MockDeviseUser
def is_password_required?
puts password_required?
end
end
unrequired_password_user = User.new
unrequired_password_user.define_singleton_method(:password_required?) { false }
unrequired_password_user.is_password_required?
regular_user = User.new
regular_user.is_password_required?
#false
#true
You can now use the DeviseInvitable gem for this.
It allows you to do exactly what you're asking.
If you need to completely overwrite the password requirement just define the following on your model:
def password_required?
false
end
So I wrote an app before that allowed for the standard way of encrypting a password using this and it worked fine:
before_save :create_hashed_password
Then:
def create_hashed_password
# validation code not shown
self.password = Digest::SHA1.hexdigest(password)
end
The problem is now in this app is that I have other user attributes I want to edit and every time I edit and save, I am hashing the already hashed password, thus making login impossible after updating.
I tested this in irb and it works:
irb(main):008:0> t.password = 'password'
=> "password"
irb(main):009:0> t.password_changed?
=> true
But when I use this line in the before filter:
before_save :create_hashed_password if password_changed?
It fails with the following error:
NoMethodError: undefined method `password_changed?' for User(no database connection):Class
(And before you ask, yes I do have a db connection, it's just with the User model because the before filter is there)
BTW I'm on Rails 4.
Try with:
before_save :create_hashed_password, if: :password_changed?
Short explanation: in your current syntax, the if part is not a param to the before_save method, this is why you need to add the coma, to send it as a parameter. Now it tries to call a class method: User.password_changed?, this doesn't make sense since you need to perform an instance method against a user object.
Try this:
before_save :create_hashed_password, if: Proc.new { &:password_changed? }
Hope this helps, happy coding
I am working on a rails 3.2.13 project. I am using devise plugin (devise gem 3.2.2, 1.4.2) for authentication. Using this plugin, how can I validate the current_password field while changing the old password to a new one? Or else, please suggest how I can achieve this by encrypting the given string and matching it with the password already saved without using the devise plugin.
E.g.: One user has encrypted_password like below:
"$2a$10$VrawKYj6zp10XUxbixVzE.7d4QgYjQn9aiuzAuP7fp3PZOLMP5wbu"
while changing the password, if I enter a current_password, it should match the string above (encrypted_password == current_password). How can I validate this?
I believe you need to break your problem down into the following steps:
Determine if the old_password is actually the user's current password.
To do this, you can call:
User.find_by_id([SOME_ID]).valid_password?(old_password)
If this returns true, then you can move on to the next step to begin changing of the password. If it doesn't, then the old_password is incorrect, and you should not allow the changing of password.
The implementation of valid_password? can be found in the Devise gem's /lib/devise/models/database_authenticatable.rb file (at around Line 40). You could use this implementation to roll your own code for validating a password. But, Devise pretty much does it for you if you call valid_password?, so rolling your own seems unnecessary.
If old_password is valid, then verify that new_password matches confirm_new_password.
if (new_password == confirm_new_password)
.
.
.
end
If these match, then set the new password by doing the following:
u = User.find_by_id([SOME ID])
u.password = new_password
u.password_confirmation = confirm_new_password
u.save
You can verify that the password has been changed by:
u.valid_password?(new_password)
Update user with current_password validation:
#user.update_with_password(account_update_params)
# account_update_params - should have :current_password, :password, :password_confirmation
It is default behaviour in Devise::RegistrationsController. If you want update user without password, you should overwrite controller's action
class UsersController < Devise::RegistrationsController
def update_resource(resource, params)
# resource.update_with_password(params)
resource.update_attributes(params)
end
end
Do I understand you right what you want allow users login with encrypted and unencrypted (usual) password?
We have:
user.valid_password?('Password2').should
code on github
So we can overwrite it inside models/user.rb
def valid_password?(password)
encrypted_password == password || super(password)
end
I'm a bit confused about this mass assignment issue. Here's my question
Say I have a user model with the following attributes:
name
login
password
email
During an edit, the update method is triggered:
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
....
end
In my head it makes sense to protect most, if not all, of these attributes as i wouldn't want the password/email/login to be compromised. So I would do this in the model
attr_accessible :name
So every other attribute, asides from name, wouldn't be able to be mass assigned.
If I do this how would the valid edit form work though? Do I need to assign attributes one by one in the update method #user.email = params[:user][:email], etc? Or am I misunderstanding something (probably)?
Thanks!
Edit:
To be more specific:
Usually you see examples with the admin attribute protected. And it makes sense.
But what about the password or email attributes? Those aren't usually protected. Why wouldn't the password be protected or the email? It could mean that potentially somebody could reset the email and do a password reset or reset the password attribute and gain access to the system, no?
Watch this railscasts http://railscasts.com/episodes/26-hackers-love-mass-assignment/
You are thinking about mass assignment security the wrong way. attr_accessbile does not make the password value open to the public (you will use filter_parameter to hide that value).
Think of it this way, you have a user form. You want the user to be able to create an account with a password but you do not want them to be able to add themselves as an admin (they could do this through sql injection or manipulating the POST parameters). To protect against this, you would add :name, :password, :email to attr_accessible and leave out the admin field.
The idea is to filter the params in your controller, as described here.
class PeopleController < ActionController::Base
# This will raise an ActiveModel::ForbiddenAttributes exception because it's using mass assignment
# without an explicit permit step.
def create
Person.create(params[:person])
end
# This will pass with flying colors as long as there's a person key in the parameters, otherwise
# it'll raise a ActionController::MissingParameter exception, which will get caught by
# ActionController::Base and turned into that 400 Bad Request reply.
def update
redirect_to current_account.people.find(params[:id]).tap do |person|
person.update_attributes!(person_params)
end
end
private
# Using a private method to encapsulate the permissible parameters is just a good pattern
# since you'll be able to reuse the same permit list between create and update. Also, you
# can specialize this method with per-user checking of permissible attributes.
def person_params
params.required(:person).permit(:name, :age)
end
end