Forum: Ruby on Rails Hiding a model function

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Luke I. (Guest)
on 2007-01-19 22:59
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.
(Guest)
on 2007-01-20 00:12
(Received via mailing list)
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
Luke I. (Guest)
on 2007-01-24 19:50
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?
Jeremy E. (Guest)
on 2007-01-24 23:19
(Received via mailing list)
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.
Chris H. (Guest)
on 2007-01-25 15:01
(Received via mailing list)
"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'?
Mark C. (Guest)
on 2007-01-26 22:02
(Received via mailing list)
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<><
Luke I. (Guest)
on 2007-01-29 16:16
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.
Alan F. (Guest)
on 2007-01-29 17:19
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... for
how it generates a user model which uses this pattern.

A.
Luke I. (Guest)
on 2007-01-29 17:25
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... 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?
Darushin (Guest)
on 2007-01-29 17:49
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... 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
Alan F. (Guest)
on 2007-01-29 18:14
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 :-)   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
Chris H. (Guest)
on 2007-01-29 19:17
(Received via mailing list)
Alan and Ben beat me to it :)

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
Luke I. (Guest)
on 2007-01-29 19:53
Chris H. wrote:
> Alan and Ben beat me to it :)
>
> 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.
Chris H. (Guest)
on 2007-01-29 20:57
(Received via mailing list)
i would argue 'what does it HURT?'.
Luke I. (Guest)
on 2007-01-29 21:17
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.
Jason R. (Guest)
on 2007-01-29 21:24
(Received via mailing list)
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
Luke I. (Guest)
on 2007-01-29 21:25
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.
Luke I. (Guest)
on 2007-01-29 22:18
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.
Luke I. (Guest)
on 2007-01-29 22:21
> 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.
Benjamin Ritcey (Guest)
on 2007-01-29 22:29
(Received via mailing list)
Luke I. wrote:
>>   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.
>

Don't do that.  The salt is there for a reason - it makes creating
comprehensive lookup tables that much harder.  If you hardcode the salt,
you make it that much easier for someone to generate a lookup table that
covers _all_ of your passwords.  You don't "crack the salt" - the salt
adds complexity to the password.

http://en.wikipedia.org/wiki/Salt_(cryptography) explains it well.
(Guest)
on 2007-01-30 09:22
(Received via mailing list)
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>
This topic is locked and can not be replied to.