Copy devise users after db:reset - ruby-on-rails

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.

Related

Data migration problems when you explicitly set ID's (Rails + Postgres)

I'm writing a script for a big migration and have come across a major issue.
# Import users
user_data.each do |data|
u = User.new
u.id = data.id
u.email = data.email
# more user attributes set...
u.save!
end
# required to prevent Postgres from trying to use now taken user ids
ActiveRecord::Base.connection.execute "ALTER SEQUENCE users_id_seq RESTART WITH #{User.last.id+1};"
So first we read user data from a data source, and set it's id manually. We need to preserve ids since we are migrating associated data as well.
Then later on, we need to create more users conditionally from the data of an associated object.
# Create a user for this email if no user with this email exists.
if data.email
user = User.find_by_email(data.email)
if user
o.user = user
else
o.user = User.create!(
first_name: 'Unknown',
last_name: 'Unknown',
email: data.email,
password: generate_temp_password
)
end
end
This fails at User.create! with:
Validation failed: Email has already been taken (ActiveRecord::RecordInvalid)
I've debugged this a bit and can see that User.where(email: data.email).first is nil right before this error is thrown. I suspect this has something to with setting ids beyond the current auto increment value, somehow causing the new records to be invisible in my queries, but visible to Postgres own validations.
So how can a user with a specific email not be present, but still trigger DB validation errors?
Apparently, Devise downcases email addresses. And the offending email had some caps in it. So it missed a case sensitive check, and then failed as a dupe when case insensitive.
Devise outsmarted me it seems.

Devise: manually encrypt password and store directly

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

Rails expire password within 24 hours

In my rails 3.1 application, I want to create and expire random password for users.I am using devise gem for that.Any plugin available for expiring password withing some duration?
Or else Please give me some logical advice to implement this feature.
Please consider me as a newbie.
It sounds like you just want to expire the password once. If you're looking to do it at regular intervals (e.g. every couple of months) or if you want to prevent users from re-using passwords, it gets more complicated.
Taken from an app I'm working on:
app/models/user.rb (assuming that's what you name your model):
def password_should_expire?
# your logic goes here, remember it should return false after the password has been reset
end
app/controllers/application_controller.rb
before_filter :check_password_expiry
def check_password_expiry
return if !current_user || ["sessions","passwords"].include?(controller_name)
# do nothing if not logged in, or viewing an account-related page
# otherwise you might lock them out completely without being able to change their password
if current_user.password_should_expire?
#expiring_user = current_user # save him for later
#expiring_user.generate_reset_password_token! # this is a devise method
sign_out(current_user) # log them out and force them to use the reset token to create a new password
redirect_to edit_password_url(#expiring_user, :reset_password_token => #expiring_user.reset_password_token, :forced_reset => true)
end
end
When you create a password, note the time it was created. Then, when the password is being used, check that the password was created less than 24 hours ago.
Depending on what frameworks you are using, this functionality (or something similar) may already exist within the framework, or perhaps as a plugin. If not, it isn't particularly difficult to implement. All you would need is an extra column in your data store to hold the password creation date/time, and a bit of extra logic on password creation and on password use.
Check out the Devise Security Extension gem:
https://github.com/phatworx/devise_security_extension
I've been using it for expiring passwords and archiving passwords (to make sure an old password is not reused) with no problems.
#Jeriko's answer contains some old code, here are the edits
In model/user.rb:
def password_should_expire?
if DateTime.now() > password_changed_at + 30.seconds
return true;
else
return false;
end
end
In Application Controller:
before_filter :check_password_expiry
def check_password_expiry
return if !current_user || ["sessions","passwords"].include?(controller_name)
# do nothing if not logged in, or viewing an account-related page
# otherwise you might lock them out completely without being able to change their password
if current_user.password_should_expire?
#expiring_user = current_user # save him for later
#expiring_user.set_reset_password_token! # this is a devise method
sign_out(current_user) # log them out and force them to use the reset token to create a new password
redirect_to edit_password_url(#expiring_user, :reset_password_token => #expiring_user.reset_password_token, :forced_reset => true)
end
end

How to use Cucumber/Factory Girl with Persistent Roles

I use db/seeds.rb to populate my database with 2 user roles ("Admin", "User") that will never change. When i run tests though, the seed data does not get carried over and the results are error tests.
When i try to run cucumber i get the following error:
Using the default profile... Feature: Sign in In order to get access
to protected sections of the site A valid user Should be able to
sign in
Scenario: User is not signed up #
features/users/sign_in.feature:6 Not registered: role
(ArgumentError)
/Users/x/.rvm/gems/ruby-1.9.2-p180/gems/factory_girl-2.0.0.rc4/lib/factory_girl/registry.rb:15:in
find'
/Users/x/.rvm/gems/ruby-1.9.2-p180/gems/factory_girl-2.0.0.rc4/lib/factory_girl.rb:39:in
factory_by_name'
/Users/x/.rvm/gems/ruby-1.9.2-p180/gems/factory_girl-2.0.0.rc4/lib/factory_girl/syntax/vintage.rb:53:in
default_strategy'
/Users/x/.rvm/gems/ruby-1.9.2-p180/gems/factory_girl-2.0.0.rc4/lib/factory_girl/syntax/vintage.rb:146:in
Factory'
/Users/x/rails/ply_rails/features/support/db_setup.rb:6:in
`Before'
Given I am not logged in #
features/step_definitions/user_steps.rb:36
Here is what my setup looks like:
# features/support/db_setup.rb
Before do
# Truncates the DB before each Scenario,
# make sure you've added database_cleaner to your Gemfile.
DatabaseCleaner.clean
Factory(:role, :name => 'Admin')
Factory(:role, :name => 'User')
end
# features/users/sign_in.feature
Feature: Sign in
In order to get access to protected sections of the site
A valid user
Should be able to sign in
Scenario: User is not signed up # THIS IS LINE 6
Given I am not logged in
And no user exists with an email of "user#user.com"
When I go to the sign in page
And I sign in as "user#user.com/password"
Then I should see "Invalid email or password."
And I go to the home page
And I should be signed out
# features/step_definitions/user_steps.rb
Given /^I am a "([^"]*)" named "([^"]*)" with an email "([^"]*)" and password "([^"]*)"$/ do |role, name, email, password|
User.new(:name => name,
:email => email,
:role => Role.find_by_name(role.to_s.camelize),
:password => password,
:password_confirmation => password).save!
end
Not sure where to start on getting this working, thank you for your help/time!
Well the point of tests is to start with a clean database, i.e a consistent state, so it's kind of good that everything gets wiped.
Secondly, in terms of cucumber, you should be defining a Background block to do the set up. This will run for each scenario, and has the benefit of every action being explicitly known. This is especially useful if you use the cucumber scenario plain text to show to clients. So you should do something like:
Background:
Given that the role "Admin" exists
And that the role "User" exists
Scenario:
etc
And make custom steps for the that the role [blank] exists that will create the role for you.

after_create callback not working in test but works in console

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)

Resources