Is it possible to limit which fields get persisted? - ruby-on-rails

I've got a User model that is utilizing mongoid. The model has a password, password_confirmation and encrypted_password field. The password and password_confirmation fields are populated at runtime with the value the user would type on the screen when creating a new user. When I persist, I don't want to persist the unencrypted password values, I only want to persist the value contained in encrypted_password. Is this possible? Is there something I can use to denote certain fields as not being persistable?
Thanks in advance
Chris

Here's a way:
Model only needs the password field and use a before_filter:
def User
before_save :hash_password
attr_accessible :password, :password_confirmation
def hash_password
#todo: improve by adding a salt
self.password = Digest::SHA1.hexdigest(self.password)
end
end
Notes:
Passwords should be stored using a one-way hash, and so passwords should not be 'decryptable'
Use a salt (a random value) and add that to the password before passing it to the hexdigest(). Store the salt in the database as well - say a column called password_salt.
password_confirmation is a virtual attribute and does not need to be defined in the model (rails will manage the details internally)

Related

devise :password and :encrypted_password

in devise rails gem, what is the difference between these two?
I have seen the :encrypted_password field in the generated schema but not :password field.
Any explanations if these two are interconnected with each other?
Devise::Models::DatabaseAuthenticatable has a setter for that:
def password=(new_password)
#password = new_password
self.encrypted_password = password_digest(#password) if #password.present?
end
So you should set the password as if there was a password field, devise will take care of encrypting it.
As #spickermann already pointed out - plain text password should never be stored anywhere and should filtered out from logs/error messages and so on, because this produces a huge security risk: the encrypted password leak is not totally harmless, but not dangerous that much.
password is the plain text the user set as his password (and plain text password should never be stored anywhere). encrypted_password this the encrypted version of this password.
You might want to read SecurePassword.
:password is the password that a user sets through a form.
:encrypted_password is what is stored in your database and retrieved to create a user session.
Essentially, :encrypted_password is a hashed and salted version of :password that can be safely stored in your database. See this answer if you need more information on why you shouldn't store plain text passwords in your database.

Use Devise without a password

Is there a way I can tell Devise not to use a password? I'm using Devise with LDAP so the encrypted password field is a wasted column (the password is stored in the LDAP directory). If I delete the encrypted_password column and then add some dummy accessors on my models
def encrypted_password
""
end
def encrypted_password=dummy
end
I can at least get rid of the column in the database. However, when I create new users I still have to supply a dummy password
User.create(:first_name => "Dummy", :password => "dummyPass1", :password_confirmation => "dummyPass1")
Is there a setting somewhere that would make this cleaner?
You can take "database_authenticatable" out of the devise config line in your User model, and drop the encrypted_password field entirely.

has_secure_password saves a secured password on create, not on update

I'm using the "has_secure_password" way to store a secure password in the database. When creating an user by the admin (in my app users are created, users can't create an account themselves), in the user model the password_digest is filled by a method to create a random password (see code). When the record is then saved, it is saved secure. So the user method is creating a password_digest say "TY5665^%^", then it is saved in the database say "Y^6&$d%$56GFT". Great!
before_validation :create_random_password, :on => :create
def create_random_password
self.password_digest = SecureRandom.hex(5)
end
But when the new user logs in and changes his password in his profile, the new password gets saved OK, but unsecured! Say the user is changing it to "password1", it also gets saved as "password1" in the database. So why is the secure password working on create, but not on update?
Without seeing your update code, make sure your update is to :password, not :password_digest. The magic behind the creation of a password hash to go into :password_digest only starts with :password.
Surprised that you can save your own password directly onto :password_digest and it would work when authenticating. I'd think it would take the password provided by the user, hash it, and then compare the hash to :password_digest (which couldn't be their password).
This is what I do, which may solve the issue:
before_create :set_temporary_password
def set_temporary_password
self.temporary_password = SecureRandom.hex(5)
end
Send the user self.temporary_password, and when they update it, change temporary_password to nil. Then you can know when a user has a temporary password that requires changing.

Proper flow for processing non-displayed database fields in RoR forms?

I have the following database table:
account
-
id
first_name
last_name
email_address
password_md5_hash
last_logged_in
last_ip_address
date_time_created
As you can see, an account record has several fields with values the user will not enter themselves. For instance, the password_md5_hash will be the hashed value of the password they enter, and date_time_created will be set to the current date/time when the record is created.
So, my form will not necessarily contain entry fields for all values, or the values entered will be manipulated before being written to the database (the same would be true if I wanted to strip HTML from a text field before saving to the database).
What would be the proper flow for 1) hashing the password entered in the form and storing that and 2) setting and storing the date_time_created field's value in the database on form submission? These things would take place in the model, no? Can someone provide a code sample or link to one that explains this?
Thanks.
The best way would probably to use an ActiveRecord callback, I will use an accessor here so when you create User.new(:password => "something") in your controller, it'll be assigned to something that's available, but not stored in the database.
class User < ActiveRecord::Base
attr_accessor :password
before_save :hash_password
...
private
def hash_password
attribute(:password_md5_hash => Digest::MD5.hexdigest(password))
end
end
I'm not that an MD5 is the best way to store passwords (I think the general accepted practice is to use a hash and a salt, check out the way restful_authentication or authlogic does it for best practices)
For date_time_created, you should checkout the 'created_at' and 'updated_at' fields that are already given to you. If you need another column, you can use the same callback technique if you need to manipulate it before saving to the database.
When using the attr_accessor, my understanding is you're basically creating a reader and writer, so:
attr_accessor :password
is equal to
#Reader
def password
#password
end
#Writer
def password=(pwd)
write_attribute( :password, pwd )
end
In cases like this where you need to modify the field before saving the model's info to DB, you just need to manually create the writer and modify it how you wish... so in this case take the writer method and use it to create the salt and encrypt... something like:
def password=(pwd)
#password = pwd
return if pwd.blank?
create_new_salt
self.password_md5_hash = User.encrypted_password(self.password, self.salt)
end
Examples of the methods used above:
def create_new_salt
self.salt = "#{object_id}#{rand}"
end
def self.encrypted_password(password, salt)
hashme = password + "morerandomtexthere" + salt
Digest::SHA1.hexdigest(hashme)
end
Also, for the date_time_created, just use "t.timestamps" in your migration and if I'm not mistaken, Rails will handle those for you.

How can I cause a setter method to encrypt data before setting it in ActiveRecord?

I'd like to implement a Rails User model that has a DB column called password. I want to make it so that when I call...
user_instance.password = 'cleartext'
the method hashes the cleartext before setting it on the instance like so:
Digest::SHA1.hexdigest(cleartext)
I've tried using a callback, but the problem is that is hashes the pw every time the user is saved, even if the pw isn't updated. So it gets hashed and rehashed over and over.
I tried redefining the password= method...
alias password= old_password=
def password=(cleartext)
old_password=(Digest::SHA1.hexdigest(cleartext))
end
but got an error saying password= does not exist.
FYI, you may want to check out restful_authentication plugin as it will do this for you. Why roll your own?
The way acts_as_authenticated does it:
Database/model has a column called "encrypted_password"
Create a virtual attribute called password
Password isn't populated on a find(..) so if the password is blank, don't encrypt
If the password is nonblank, that means the user entered one, so go ahead and encrypt and stuff in encrypted_password
Code snip (random copy-paste from my user class, so don't blindly paste this in):
require 'digest/sha1'
class User < ActiveRecord::Base
# stuff
# callback
before_save :encrypt_password
attr_accessor :password
# methods
def encrypt_password
return if password.blank?
salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
crypted_password = Digest::SHA1.hexdigest("--#{salt}--#{self.password}--")
end
In response to Ethan's comment, what is a virtual attribute, a virtual attribute is one that is not in the database, like Matt's attr_accessor :password, this way you can accept that input from the user, but no necessarily store it in that form, in this instance we want to be able to accept a clear text password, but we want to store it encrypted. To do this we have a virtual attribute :password, and in the database we store it as encrypted_password.
Well, you could override the setter:
def password=(value)
self[:password] = Digest::SHA1.hexdigest(value)
end
And this would always encrypt the value. You don't need a before_save or an attr_accessor.
Note that it might be a good idea to choose a random n-bit value per user (upon creation of that user) and prepend that to the password when you hash it.
The reason being: if anyone gets a hold of your database, not only can't they immediately see the users' password, they also can't see if two users have identical passwords (important if one of those users grabs your database) and a certain class of hash-cracking attacks (rainbow tables) becomes harder.

Resources