Devise模型运行多次? [英] Devise models run before_save multiple times?
问题描述
我的客户端希望所有用户数据加密,所以我创建了一个 before_save
和 after_find
使用 Gibberish
b before_save UserEncryptor.new
after_find UserEncryptor.new
#user_encryptor.rb
class UserEncryptor
def initialize
@cipher = Gibberish :: AES.new (password)
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name )
user.email = encrypt(user.email)除非没有user.confirmed?或user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email)除非没有user.confirmed?或user.unconfirmed_email
end
private
def encrypt(value)
@ cipher.enc(value)
end
def decrypt(value)
@ cipher.dec(value)
end
end
嗯,当用户首先使用 Devise
注册时,模型看起来就像是应该的。但是一旦用户确认,如果我检查用户, first_name
和 last_name
属性看起来已被加密多个倍。所以我在 before_save
方法中放置一个断点,然后点击确认链接,我看到它连续执行了三次。结果是加密值再次被加密,然后再次被加密,所以下次我们检索记录,并且每次之后,我们得到两倍加密的值。
现在,为什么会发生这种情况?对于执行相同逻辑的其他非设计模型,不会发生这种情况。 Devise
将$ code> current_user 缓存在几个不同的地方,并将用户保存在每个位置?在执行下一个 before_find
之前,还可以调用 before_save
回调3次?
更重要的是,当我使用 Devise
时,如何成功加密我的用户数据?我也有 attr_encrypted
和 devise_aes_encryptable
的问题,所以如果我得到很多这些建议,那我猜我有一些更多的问题要发布: - )
我在同事的帮助下解决了我的问题。
$ b
对于加密名字和名字,只需向模型添加一个标志,表示是否已加密。这样,如果发生多个保存,则该模型知道它已经被加密,并且可以跳过该步骤:
def before_update(user)
除非user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
如果user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user .last_name)
user.encrypted = false
end
end
对于电子邮件地址,这还不够。 Devise正在做一些非常奇怪的事情,重置缓存的值,所以电子邮件地址仍然被加密。因此,我们在使用者模型时忽略了回调来加密电子邮件地址:
def email_before_type_cast
super.present? ? AES.decrypt(super,KEY):
end
def电子邮件
返回除非self [:email]
@email || = AES。 $($)
def email =(provided_email)
self [:email] = encrypted_email ($)
def self.find_for_authentication(conditions = {})
条件[:email] = encrypted_email(条件[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes,attributes,error =:invalid)
属性[:email] = encrypted_email(attributes [:email])if属性[:email]
super
end
def self.encrypted_email decryptpted_email
AES.encrypt(decryptpted_email,KEY,{:iv => IV})
end
这让我们大部分的方式。然而,我的设计模型是可以重见的,所以当我改变用户的电子邮件地址并尝试保存时,可重复的模块遇到了一些时髦的事情,记录被保存了一百次左右,然后我得到了一个堆栈溢出和一个回滚。我们发现我们需要在用户模型上重写一个方法来做这个窍门:
def email_was
super 。当下? ? AES.decrypt(super,KEY):
end
现在我们所有的个人可识别信息被加密! Yay!
My client wants all user data encrypted, so I've created a before_save
and after_find
call back that will encrypt certain properties using Gibberish
:
# user.rb
before_save UserEncryptor.new
after_find UserEncryptor.new
# user_encryptor.rb
class UserEncryptor
def initialize
@cipher = Gibberish::AES.new("password")
end
def before_save(user)
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.email = encrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
def after_find(user)
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.email = decrypt(user.email) unless not user.confirmed? or user.unconfirmed_email
end
private
def encrypt(value)
@cipher.enc(value)
end
def decrypt(value)
@cipher.dec(value)
end
end
Well, when the user first signs up using Devise
, the model looks about like it should. But then once the user confirms, if I inspect the user, the first_name
and last_name
properties look to have been encrypted multiple times. So I put a breakpoint in the before_save
method and click the confirmation link, and I see that it's getting executed three times in a row. The result is that the encrypted value gets encrypted again, and then again, so next time we retrieve the record, and every time thereafter, we get a twice encrypted value.
Now, why the heck is this happening? It's not occurring for other non-devise models that are executing the same logic. Does Devise
have the current_user
cached in a few different places, and it saves the user in each location? How else could a before_save
callback be called 3 times before the next before_find
is executed?
And, more importantly, how can I successfully encrypt my user data when I'm using Devise
? I've also had problems with attr_encrypted
and devise_aes_encryptable
so if I get a lot of those suggestions then I guess I have some more questions to post :-)
I solved my problem with the help of a coworker.
For encrypting the first and last name, it was sufficient to add a flag to the model indicating whether or not it's been encrypted. That way, if multiple saves occur, the model knows it's already encrypted and can skip that step:
def before_update(user)
unless user.encrypted
user.first_name = encrypt(user.first_name)
user.last_name = encrypt(user.last_name)
user.encrypted = true
end
end
def after_find(user)
if user.encrypted
user.first_name = decrypt(user.first_name)
user.last_name = decrypt(user.last_name)
user.encrypted = false
end
end
For the email address, this was not sufficient. Devise was doing some really weird stuff with resetting cached values, so the email address was still getting double encrypted. So instead of hooking into the callbacks to encrypt the email address, we overrode some methods on the user model:
def email_before_type_cast
super.present? ? AES.decrypt(super, KEY) : ""
end
def email
return "" unless self[:email]
@email ||= AES.decrypt(self[:email], KEY)
end
def email=(provided_email)
self[:email] = encrypted_email(provided_email)
@email = provided_email
end
def self.find_for_authentication(conditions={})
conditions[:email] = encrypted_email(conditions[:email])
super
end
def self.find_or_initialize_with_errors(required_attributes, attributes, error=:invalid)
attributes[:email] = encrypted_email(attributes[:email]) if attributes[:email]
super
end
def self.encrypted_email decrypted_email
AES.encrypt(decrypted_email, KEY, {:iv => IV})
end
This got us most of the way there. However, my Devise models are reconfirmable, so when I changed a user's email address and tried to save, the reconfirmable module encountered something funky, the record got saved like a hundred times or so, and then I got a stack overflow and a rollback. We found that we needed to override one more method on the user model to do the trick:
def email_was
super.present? ? AES.decrypt(super, KEY) : ""
end
Now all of our personally identifiable information is encrypted! Yay!
这篇关于Devise模型运行多次?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!