charredUtensil
charredUtensil

Reputation: 152

Devise + Legacy DB: "password" column

I'm trying to slowly migrate an old CakePHP site to Rails 4. Because of the massive effort required, the site needs to be moved over gradually, only migrating a single piece at a time, and starting with Active Admin. I've seen plenty of questions about doing things like this, but there are a few tricky extra things I need to consider:

  1. I cannot modify the CakePHP code
  2. I cannot break the CakePHP site
  3. The CakePHP site stores its hashed passwords in a database column named "password", and Devise doesn't like that. It expects "password" to be the plaintext password, and has gotten into a nasty habit of attempting to override that DB column as well.

Once the entire site has been migrated to Rails, I can certainly start following any of the myriad number of answers about how to migrate an app to Devise, but for now I need to work with the existing app

Thanks

Upvotes: 0

Views: 1005

Answers (2)

Rystraum
Rystraum

Reputation: 2025

I found this: http://www.slideshare.net/napcs/rails-and-legacy-databases-railsconf-2009 and followed his advise in creating sql views to wrap up the legacy table.

This is what I used to run on mine (I'm adding an application on top of Limesurvey):

CREATE VIEW `users` AS
SELECT `uid` as `id`,
       `users_name` as `username`,
       `email` as `email`,
       `password` as `encrypted_password`,
       `full_name` as `full_name`,
       `role_names` as `role_names`,
       `parent_id` as `parent_id`,
       `lang` as `lang`,
       `one_time_pw` as `one_time_pw`,
       `created` as `created_at`,
       `modified` as `updated_at`
FROM `lime_users`;"

The catch is that you have to include in the sql view all columns that do not have default values in the base table for you to insert into the view. This is a good way to normalize 'ugly' column names into rails-friendly column names.

My user model:

class User < ActiveRecord::Base
  before_save :save_encrypted_password
  self.primary_key = "id" # This needs to be declared, for some reason.

  def sha2(password)
    (Digest::SHA2.new << password).to_s
  end

  def valid_password?(password)
    return false if encrypted_password.blank?
    return Devise.secure_compare(sha2(password), self.encrypted_password)
  end
protected
  def save_encrypted_password
    if password == password_confirmation
      self.encrypted_password = sha2(password)
    else
      errors.add :password_confirmation, "has to match password"
    end
  end
end

It's also a good idea to reflect in the model constraints that are in the database (not null, unique values, etc) to avoid any more gotchas. (Learned this the hard way after almost an hour of slogging through arcane error messages.)

Hope this helps.

Upvotes: 0

charredUtensil
charredUtensil

Reputation: 152

I was able to resolve this issue by installing the ignorable gem, and SELECTing the password column AS encrypted_password in the default scope. It's ugly, but it works

class User < ActiveRecord::Base
  ignore_columns :password

  default_scope :select => "#{User.quoted_table_name}.*, #{User.quoted_table_name}.password AS encrypted_password"

  devise :database_authenticatable

  def valid_password?(password)
    hash = ::Digest::MD5.hexdigest("[SALT REDACTED]#{password}").downcase
    return hash == self.encrypted_password
  end
end

Upvotes: 1

Related Questions