I'm trying to migrate a ton of users from an old database. To do this, I'm using activerecord-import and trying to save all my user data directly to DB (bypassing the User model).
My issue: I need to take the old user's plain-text password, encrypt it, and store directly to the DB. I know how to generate a password using Devise, but am wondering if there's a way to get a hashed password that I can store directly to the database.
Hoping to do:
new_hashed_password = Devise.awesome_encrypting_method(old_user.password)
Then store "new_hashed_password" directly into the DB without going through the model. I dug around in Devise and found this:
def password_digest(password)
::BCrypt::Password.create("#{password}#{self.class.pepper}", :cost => self.class.stretches).to_s
end
##stretches defaults to 10 (lib/devise.rb:71) and isn't overridden by my initializer
##pepper defaults to nil (lib/devise.rb:148) and isn't overridden by my initializer
I thought I could manually re-create password_digest() but I think I'm missing something fundamental about Bcrypt because even with setting password and stretches, the resulting hash is different every time.
Any ideas? Thanks for your help!
You should do it like this:
password = 'the secret password'
new_hashed_password = User.new(:password => password).encrypted_password
This is much better than using BCrypt directly as it abstracts away how passwords are generated from your code, making it easier to understand, and also immune to changes in how devise constructs encrypted passwords. Your code should not, and has no reason to know anything about that.
Good news and bad news.
Good news:
The following works to create your user's password manually.
pepper = nil
cost = 10
encrypted_password = ::BCrypt::Password.create("#{password}#{pepper}", :cost => cost).to_s
You can find your pepper and cost in your devise initializer. This method was confirmed using Devise's "valid_password?" method.
Bad news:
The entire reason I was trying to avoid "User.new(password: password).encrypted_password" was because of speed. It's terribly slow. With all my other pieces of my import task, I've intentionally avoided this.
But as it turns out, the major cost here is not instantiating a User object -- but BCrypt itself. There is very little noticeable speed boost when using BCrypt directly because it's intentionally designed to be slow.
My final answer: suck it up, run the rake script, go find a beverage.
None of the other answers above worked for me, so here is what I did:
user.valid_password?(plain_password)
https://github.com/plataformatec/devise/blob/d293e00ef5f431129108c1cbebe942b32e6ba616/lib/devise/models/database_authenticatable.rb#L44
An alternative method is: User.new.send(:password_digest, 'xxx')
Assuming you have a mysql database with a "users" table and a "password" column
And an ActiveRecord model class called "user" that is hooked up to devise
Create an ActiveRecord model class in your app
app/models/old_user.rb
OldUser < ActiveRecord::Base
set_table :users
establish_connection :database => "old_database", :user => "old user", :adapter => "mysql"
end
then create a rake task:
app/lib/tasks/migrate_users.rake
task :migrate_users => :environment do
OldUser.find_each do |old_user|
u = User.new(:email => old_user.email, :password => old_user.password, :password_confirmation => old_user.password);
#if your using confirmation
u.skip_confirmation!
u.save!
end
end
Modify as necessary (make sure you're saving any app-specific user attributes)
Then$ rake migrate_users
Related
I'm currently going through Michael Hartl's book (Ruby on Rails tutorial 3rd edition) and he uses password_digest with his authentication model, I checked out Pragmatic Studio's Rails course and in their sample video, they use passowrd_hash/password_salt in their authentication model. From wikipediaing, it seems that salt is a standard use in cryptography and has a variety of uses for security. All I could really find on password_digest is it works with a rails method has_secure_password.
Is one preferred over the other or do they serve different purposes ultimately?
when using the has_secure_password a lot of the encryption magic is done automatically for you, and a few functions are exposed that make it easier for you to verify the password of a User.
As taken from the has_secure_password documentation:
# Schema: User(name:string, password_digest:string)
class User < ActiveRecord::Base
has_secure_password
end
user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
user.save
# => false, password required
user.password = 'mUc3m00RsqyRe'
user.save
# => false, confirmation doesn't match
user.password_confirmation = 'mUc3m00RsqyRe'
user.save
# => true
user.authenticate('notright')
# => false
user.authenticate('mUc3m00RsqyRe')
# => user
User.find_by(name: 'david').try(:authenticate, 'notright')
# => false
User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe')
# => user
Both systems you mentioned will work, it just comes down on what you wish to use, what options you have available etc.
Manually building a salted hash (based on e.g. SHA1) was indeed a long time the default way to store passwords. However, since a couple of years, it became clear that there need to be better (i.e. more secure) ways to store a password which ensure that even if an attacker could gain access to the raw hashes, they could not easily brute-force them.
As such, in newer Rails versions, passwords are by default saved with has_secure_password using the Bcrypt algorithm which is much more suitable for password storage than a simple salted hash. Note that Bcrypt also uses a salt internally. You just don't have to deal with it yourself.
The method Hartl uses, has_secure_password, uses an algorithm called Bcrypt to salt and hash a password, producing a password_digest string that has three components:
A flag that indicates that the string is a Bcrypt hash
The (unhashed) salt
The result of hashing password+salt
I'm not familiar with the Pragmatic Studio course, but it sounds like they're storing the salt and hash in different attributes.
I'm currently trying to write a rake task that resets my database while copying admins (specific type of devise user) to the new server.
task :safe_reset => :environment do
desc "Resets db while persisting admins."
user_collection = []
User.all.each do |user|
if user.admin?
user_collection << user.attributes
end
end
Rake::Task['db:reset'].invoke
user_collection.each do |user|
User.create!(user)
end
end
However, password information is not a public attribute of user. So I don't have enough information to create a new user essentially.
Is there a way to get password information, or preferably, is there a way to do this while avoiding reducing every admin to a hash object?
You can't get the original password information. But you can get the encrypted password...
Replace
user_collection.each do |user|
User.create!(user)
end
By this
user_collection.each do |user|
encrypted_password = user.delete('encrypted_password')
u = User.create!(user.merge({
:password => "Foobar",
:password_confirmation => "Foobar"
})
u.update_attribute(:encrypted_password, encrypted_password)
end
This shouldn't be done. Typically an application is 'seeded' (in db/seeds.rb) with one administrative user account. After the application goes live a developer uses that account to propagate the other necessary accounts and changes its details to something other than the defaults. I don't see any advantage or benefit to persisting one table like this as db:reset is generally never used in production or staging and is often used in development. In development we use our seed data to generate accounts for us.
It can be done, depending on your auth solution but I highly recommend against the practice unless there's a good use case for it.
Testing in Rails has always been something of a mystery that I avoid if possible but I'm putting a production application together that people will pay for so I really need to test. This problem is driving me mad because the test fails but when I perform the same commands in the console (in test and development mode) it works fine.
user_test.rb
test "should update holidays booked after create"
user = users(:robin)
assert_equal user.holidays_booked_this_year, 4 # this passes
absence = user.absences.create(:from => "2011-12-02", :to => "2011-12-03", :category_id => 1, :employee_notes => "Secret") # this works
assert_equal user.holidays_booked_this_year, 5 # fails
end
absence.rb
after_create :update_holidays_booked
def update_holidays_booked
user = self.user
user.holidays_booked_this_year += self.days_used # the days used attribute is calculated using a before_create callback on the absence
user.save
end
My only thoughts are that it's something to do with updating the User model through a callback on the Absence model but, as I say, this works in the console.
Any advice would be appreciated.
Thanks
Robin
What are you using for your factory?
If you are using a database backed test then you need to reload the user in the test (because the user instance is not updated, the absence's user is updated and saved to the database), reloading the user would look like:
assert_equal user.reload.holidays_booked_this_year, 5
I would also guess that an absence needs to have a user, so you should use build instead of create so the foreign key for user is part of the "created" instance:
user.absences.build
First guess would be that in the console you are operating on a real user in the database whereas the test is a fixture. Have you tried this is in test?:
raise user.inspect
Look at the output and determine which user you are actually working with and what the holidays_booked_this_year attributes is.
(your test block also needs a "do" after the description)
I'm getting a similar error to this post Ruby on Rails Authlogic password not valid "Password is not valid" which never seemed to be resolved
in the script/console if I create a new user:
myval = "foo#example.com"
u = User.create(:email => myval, :password => myval, :password_confirmation => myval)
u.valid?
>> true
u.save
>> true
u.valid_password?(myval)
>>false
if i set in my user.rb:
acts_as_authentic do |c|
c.validate_password_field = false
end
i get the same response. Any suggestions?
I just took a dig through the AuthLogic code and it looks like setting validate_password_field to false only prevents Rails from running the default validations. It has no effect on the valid_password? method.
There are a number of other factors that appear to cause it to return false. These include but may not be limited to:
Checking for a blank password
The crypted password is blank
The password doesn't match. (This one is a bit complicated because there are a variety of factors involved in this, including the CryptoProvider and whether or not you're using RestfulAuthentication style passwords.)
To give a more definitive answer, I probably need some more information on your exact setup.
I had a similar problem to this, turns out the old Restful Authentication password fields are migrated at 40 characters in length when Authlogic requires 255.
In my case it was just a messed up data base migration. rake db:migrate VERSION=0 && rake db:migrate solved this problem for me.
try to remark "before_save :encrypt_password" in user.rb if you have.
# before_save :encrypt_password
What is the best way to do per-user database connections in Rails?
I realize this is a poor Rails design practice, but we're gradually replacing an existing web application that uses one database per user. A complete redesign/rewrite is not feasible.
Put something like this in your application controller. I'm using the subdomain plus "_clientdb" to pick the name of the database. I have all the databases using the same username and password, so I can grab that from the db config file.
Hope this helps!
class ApplicationController < ActionController::Base
before_filter :hijack_db
def hijack_db
db_name = request.subdomains.first + "_clientdb"
# lets manually connect to the proper db
ActiveRecord::Base.establish_connection(
:adapter => ActiveRecord::Base.configurations[ENV["RAILS_ENV"]]['adapter'],
:host => ActiveRecord::Base.configurations[ENV["RAILS_ENV"]]['host'],
:username => ActiveRecord::Base.configurations[ENV["RAILS_ENV"]]['username'],
:password => ActiveRecord::Base.configurations[ENV["RAILS_ENV"]]['password'],
:database => db_name
)
end
end
Take a look at ActiveRecord::Base.establish_connection. That's how you connect to a different database server. I can't be of much more help since I don't know how you recognize the user or map it to it's database, but I suppose a master database will have that info (and the connection info should be on the database.yml file).
Best of luck.