Hiding a model function


#1

I’m still developing my user model, and I’ve reached a quandary.

I want to keep anyone from being able to do “user.password = ‘bob’”, but
don’t want to break any built in AR magic.

I’m not entirely sure I even know how to do that… and beyond that, I’m
not sure if I do figure out how to do that, how I’m going to actually
set the password (I intend to write a change_password function, but if I
take away the ability to do user.password = ‘blah’, then I almost
certainly can’t do self.password = ‘blah’ either)…

Any help?

Thanks.


#2

On Jan 19, 3:59 pm, Luke I. removed_email_address@domain.invalid
wrote:

I’m still developing my user model, and I’ve reached a quandary.

I want to keep anyone from being able to do “user.password = ‘bob’”, but
don’t want to break any built in AR magic.

I’m not entirely sure I even know how to do that… and beyond that, I’m
not sure if I do figure out how to do that, how I’m going to actually
set the password (I intend to write a change_password function, but if I
take away the ability to do user.password = ‘blah’, then I almost
certainly can’t do self.password = ‘blah’ either)…

Try attr_protected http://railsapi.org/attr_protected

-r

Any help?

Thanks.


Posted viahttp://www.ruby-forum.com/.


Ryan R.
http://raaum.org
http://rails.raaum.org – Rails docs
http://locomotive.raaum.org – Self contained Rails for Mac OS X


#3

removed_email_address@domain.invalid wrote:

Try attr_protected http://railsapi.org/attr_protected

-r


Ryan R.
http://raaum.org
http://rails.raaum.org – Rails docs
http://locomotive.raaum.org – Self contained Rails for Mac OS X

Sorry it took me so long to get back to this (and I appreciate the
suggestion), but I wasn’t clear on what I want (and I’m not sure it’s
possible to have what I want).

I want it to be absolutely impossible to change a user object’s password
without calling the change_password function… it shouldn’t be settable
via password=, via create(:password => blah), new(:password => blah), or
any other method… I want my model to have 100% control over the
password being changed. The link you gave lets me cut off the second
method of access, but I don’t want the first one available either: is
there a way to do that?


#4

On 1/24/07, Luke I. removed_email_address@domain.invalid wrote:

any other method… I want my model to have 100% control over the
password being changed. The link you gave lets me cut off the second
method of access, but I don’t want the first one available either: is
there a way to do that?

In your model class:

def password=(password)
end

That will silently ignore the call to password=. You can have it
raise an error if you want.


#5

Make the method private:

def change_password(pass)
self.password = pass
end

private
def password=(password)
do some stuff
self.save
end

then you can only call the password= method from within the model and
then
use model.change_password(password) to make the password change.

M<><


#6

“I want to keep anyone from being able to do “user.password = ‘bob’”,
but
don’t want to break any built in AR magic.”

the question is, who do you need to stop from doing this and why? who
is ‘anyone’?


#7

Mark C. wrote:

Make the method private:

def change_password(pass)
self.password = pass
end

private
def password=(password)
do some stuff
self.save
end

then you can only call the password= method from within the model and
then
use model.change_password(password) to make the password change.

M<><

Thank you… I ended up figuring out to do this on my own.

And re: Chris H.: setting a password, in this application, should
automatically encrypt said password. If I encapsulate the functionality
in such a way that the only possible way to change a password is via a
method I write that automatically does the encryption, I don’t have to
worry about forgetting to do it in the future. I don’t have to worry
that if any of the other developers start working on my project and are
too ignorant to figure out how it works and just try to set a password
without encrypting it.


#8

Luke I. wrote:

And re: Chris H.: setting a password, in this application, should
automatically encrypt said password. If I encapsulate the functionality

Then can I suggest you leave all the attributes as they are and simply
add

before_save :encrypt_password

def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("–#{Time.now.to_s}–#{login}–")
if new_record?
self.crypted_password = encrypt(password)
end

See http://www.agilewebdevelopment.com/plugins/acts_as_authenticated for
how it generates a user model which uses this pattern.

A.


#9

Luke I. wrote:

Alan F. wrote:

Luke I. wrote:

And re: Chris H.: setting a password, in this application, should
automatically encrypt said password. If I encapsulate the functionality

Then can I suggest you leave all the attributes as they are and simply
add

before_save :encrypt_password

def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("–#{Time.now.to_s}–#{login}–")
if new_record?
self.crypted_password = encrypt(password)
end

See http://www.agilewebdevelopment.com/plugins/acts_as_authenticated for
how it generates a user model which uses this pattern.

A.

Is there a particular advantage to one approach over the other that I’m
missing? In one, the developer (assuming a secondary developer) is
fully aware that they are calling a function that will do whatever is
necessary with the password to make it appropriate, and in the other,
it’s being done behind the scenes so that all a developer has to do is
call password=.

I’m not sure I have a preference, but if I already have it done the one
way, is there a reason I should switch to doing it the other way?

Well the above method is far less code to maintain and logically it is
more the “Ruby R.” way of doing. Also the above way guarantees that
no matter what way people try and write the password back to the DB it
will get in encrypted. I am not sure if your method covers every way
that someone could access the password now and in the future. However, I
know the filter method will because AR always calls a before_save filter
just before it calls the model’s save function.

Ben


#10

Alan F. wrote:

Luke I. wrote:

And re: Chris H.: setting a password, in this application, should
automatically encrypt said password. If I encapsulate the functionality

Then can I suggest you leave all the attributes as they are and simply
add

before_save :encrypt_password

def encrypt_password
return if password.blank?
self.salt = Digest::SHA1.hexdigest("–#{Time.now.to_s}–#{login}–")
if new_record?
self.crypted_password = encrypt(password)
end

See http://www.agilewebdevelopment.com/plugins/acts_as_authenticated for
how it generates a user model which uses this pattern.

A.

Is there a particular advantage to one approach over the other that I’m
missing? In one, the developer (assuming a secondary developer) is
fully aware that they are calling a function that will do whatever is
necessary with the password to make it appropriate, and in the other,
it’s being done behind the scenes so that all a developer has to do is
call password=.

I’m not sure I have a preference, but if I already have it done the one
way, is there a reason I should switch to doing it the other way?


#11

Darushin wrote:

Luke I. wrote:

Is there a particular advantage to one approach over the other that I’m
missing? In one, the developer (assuming a secondary developer) is

Well the above method is far less code to maintain and logically it is
more the “Ruby R.” way of doing. Also the above way guarantees that
no matter what way people try and write the password back to the DB it
will get in encrypted. I am not sure if your method covers every way
that someone could access the password now and in the future. However, I
know the filter method will because AR always calls a before_save filter
just before it calls the model’s save function.

What Ben said :slight_smile: Also, I think it more implements your intent,
which, if I read you correctly, is not to stop the password being set,
but more to ensure that if it is set, it’s always encrypted before
hitting the db.

Alan


#12

Alan and Ben beat me to it :slight_smile:

that was the reason i asked why you wanted to ‘hide’ the method.

another method is to just override the method itself

def password=(password)
self.password_salt = “some random salt value” # used to validate
password at login
self.password_hash = encrypted_pw
end


#13

Chris H. wrote:

Alan and Ben beat me to it :slight_smile:

that was the reason i asked why you wanted to ‘hide’ the method.

another method is to just override the method itself

def password=(password)
self.password_salt = “some random salt value” # used to validate
password at login
self.password_hash = encrypted_pw
end

Okay, this has prompted me to ask another question: in all of these, it
generates a new salt every time it generates a new password… is there
a particular reason for this? I understand it would be slightly more
secure… but it seems to me the added security of changing the salt
every time the password changes is basically neglible… if someone has
cracked the original salt, they’ve probably already hijacked the account
and changed the password themselves.


#14

i would argue ‘what does it HURT?’.


#15

Curious. What kind of environment are you developing in that you can’t
trust
other developers to know what’s up and to not try something stupid? It’s
not
like the regular user can do anything to the model as such.

Jason


#16

I’m just noticing that all of the suggestions so far require someone to
do something along the lines of ‘password = “blah”’, while having a
field name that is something other than password (crypted_password or
password_hash) that what they set in password= is encrypted into.

What’s to keep someone from just calling crypted_password= or
password_hash= and screwing the whole thing up?

I think I will change to a before_save encryption because, as you said,
it will always be called, no matter what.

But I’m still going to keep them from setting the field themselves (via
encrypted_password=)… I just don’t want to take any chances.

Unless I’m horribly misunderstanding what’s been suggested… which is
always possible.


#17

Luke I. wrote:

Jason R. wrote:

Curious. What kind of environment are you developing in that you can’t
trust
other developers to know what’s up and to not try something stupid? It’s
not
like the regular user can do anything to the model as such.

Jason

I’m not developing in an environment where I can’t trust the other
developers to not do something stupid: I am, however, cautious by
nature, and would rather know with absolute certainty that they can’t
do something stupid, than hope that they don’t.

Now that I’m playing with it, I’m not 100% sure that it’s possible to
make this method protected:

If I try a call to protected (protected :encrypted_password=), that
doesn’t work… it tells me that the function doesn’t exist. Even more
revelatory of my predicament is the fact that ‘alias_method password
encrypted_password’ tells me encrypted_password is an undefined
function.

These functions aren’t being defined when I think they are, or I’m doing
something completely incorrectly.

If I try to define my own function for encrypted_password= as a
protected function, that is somehow being over-written by AR, because I
can then blithely call user.encrypted_password= without it throwing a
NoMethodError.


#18

Jason R. wrote:

Curious. What kind of environment are you developing in that you can’t
trust
other developers to know what’s up and to not try something stupid? It’s
not
like the regular user can do anything to the model as such.

Jason

I’m not developing in an environment where I can’t trust the other
developers to not do something stupid: I am, however, cautious by
nature, and would rather know with absolute certainty that they can’t
do something stupid, than hope that they don’t.


#19

If I try a call to protected (protected :encrypted_password=), that
doesn’t work… it tells me that the function doesn’t exist. Even more
revelatory of my predicament is the fact that ‘alias_method password
encrypted_password’ tells me encrypted_password is an undefined
function.

These functions aren’t being defined when I think they are, or I’m doing
something completely incorrectly.

Let me append this by saying that doing:

def password
self.encrypted_password
end

works just fine, however, I also want

user.encrypted_password = ‘test’

to raise an error, and I’m not sure how (or even if I can) make it do
that.


#20

protected “encrypted_password=”

will take care of what you need. Since symbols and strings are
relatively the same, this will work fine.

Unfortunately ruby doesn’t really give you absolute power over making
sure your developers don’t go and blow their brains out. You can make
it really hard for them not to blow their brains out, but eventually
one of them will decide to say, well… why don’t I just do…

#TODO: Not hardcode this password
user.instance_eval("@attributes[‘password’] = “changeme”)
user.save!

Now I can save the password without that silly evaluation going on.
In such case, I really recommend that you fire your developer. Or
roll back his code, isn’t that what revisions were made for?

On Jan 29, 12:21 pm, Luke I. removed_email_address@domain.invalid