设计模型多次运行before_save?

时间:2012-03-27 02:03:39

标签: ruby-on-rails ruby-on-rails-3 encryption devise

我的客户希望加密所有用户数据,因此我创建了一个before_saveafter_find回调,它将使用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

好吧,当用户首次使用Devise注册时,模型看起来应该是这样的。但是,一旦用户确认,如果我检查用户,first_namelast_name属性看起来已经多次加密。所以我在before_save方法中放了一个断点,然后点击确认链接,我发现它连续执行了三次。结果是加密值再次加密,然后再次加密,因此下次我们检索记录时,每次我们获得两次加密值。

现在,为什么会发生这种情况?对于执行相同逻辑的其他非设计模型,不会发生这种情况。 Devise是否将current_user缓存在几个不同的位置,并将用户保存在每个位置?如果在执行下一个before_save之前调用before_find回调3次怎么办?

更重要的是,当我使用Devise时,如何成功加密用户数据?我也遇到了attr_encrypteddevise_aes_encryptable的问题,所以如果我得到很多这些建议,那么我想我还有一些问题需要发布: - )

1 个答案:

答案 0 :(得分:2)

我在同事的帮助下解决了我的问题。

对于加密名字和姓氏,只需在模型中添加一个标志即表明它是否已加密。这样,如果发生多次保存,模型就知道它已经加密,可以跳过这一步:

  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

对于电子邮件地址,这还不够。通过重置缓存值,Devise正在做一些非常奇怪的事情,因此电子邮件地址仍然是双重加密的。因此,我们不是挂钩回调来加密电子邮件地址,而是覆盖用户模型上的一些方法:

  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

这让我们大部分都在那里。但是,我的Devise模型是可重新配置的,所以当我更改用户的电子邮件地址并尝试保存时,可重新配置的模块遇到了一些时髦,记录被保存了大约一百次左右,然后我得到了堆栈溢出和回滚。我们发现我们需要在用户模型上覆盖另一个方法来实现这一诀窍:

  def email_was
    super.present? ? AES.decrypt(super, KEY) : ""
  end

现在我们所有的个人身份信息都已加密!耶!