Agile Dev w/Rails, pp.128-129 Hashing Passwords

Demo: In Task F, Iteration F1 (near File 54 marker), in the Rails book
by D. Thomas.

He suggests handling password hashing by using the AR callbacks.

attr_accessor :password def before_create self.hashed_password = User.hash_password(self.password) end

Is this simply to illustrate callbacks? Would it not be easier to
create a virtual accessor:

def password=(plain)
hashed_password = User.hash_password(plain)
end

I don’t like the idea of that transient plain-text field and extra
callback methods.

I’m just wondering if I’m missing something about the whole
form->action->AR->save cycle where this alternate technique would be
worse than the callback method.

I can answer my own question here in that it would be hard to enforce
password policies without a transient plain-text version to validate
against; however, the question remains: what is the best practice?

Thanks

Edward F. <epfrederick@…> writes:

Is this simply to illustrate callbacks? Would it not be easier to
create a virtual accessor:

I’m just wondering if I’m missing something about the whole
form->action->AR->save cycle where this alternate technique would be
worse than the callback method.

I can answer my own question here in that it would be hard to enforce
password policies without a transient plain-text version to validate
against; however, the question remains: what is the best practice?

I tried several ways of accomplishing the same goal; after some
trial-and-error,
I settled on the “virtual” approach you describe as being the most
simple to
understand (and so theoretically the most maintainable). Other than
complexity,
I don’t see that one method has any benefit over the other.

As you suggest in your comment about enforcing password policies, you
have to
add a little bit more code to make the virtual attribute approach work
with form
validation. Here’s what I did (inside my model):

def password=(password)
@password = password

salt = random_string(10)
hash = Digest::SHA1.hexdigest(salt + password)

self.password_salt = salt
self.password_hash = hash
end

def password
return @password if @password
return ‘password’ if not self.password_hash.blank?
end

def password_is?(password)
return Digest::SHA1.hexdigest(self.password_salt + password) ==
self.password_hash
end

Basically, my password= is the same as yours, with the addition of doing
the
salt stuff and setting an instance variable to the plain-text password.
The
instance variable is never saved, so it is indeed “transient”, as you
suggest,
but it’s available for the password method to access for purposes of
form
validation.

Since we don’t save the plain-text password we have to come up with
something
to return when the password method is called and the @password instance
variable
is no longer available–so I return the string “password”: it will pass
my field
validations, so it serves nicely. You could return the password hash,
but it’s
longer than the length limit I have on the password field in my
validations. Set
up this way I can put all my password rules in the model as field
validations,
and everything automagically works just the way you would want it to.

The third method is what I use to actually check whether the password a
user
types is correct or not; I decided to encapsulate it in the model so
none of the
rest of my code needs to know anything about salts or any other
implementation
details.

–Forrest

On Nov 15, 2005, at 4:11 PM, Edward F. wrote:

Is this simply to illustrate callbacks? Would it not be easier to
create a virtual accessor:

Basically, yes…