Migrating from devise to omniauth (identity) - ruby-on-rails-3.1

I'm thinking of moving over to Omniauth 1.0 (using the "identity" strategy or gem) from Devise 1.4.7, my question is after doing all the code conversion, views etc, will the old passwords, those user accounts created with Devise, still work with the same passwords under OmniAuth?
I've done some research and both are using bcrypt, so I'm guessing "yes" they will work as before and users won't have to create new passwords. Or am I missing something crucial?

Devise passwords are not directly compatible with omniauth-identity
It is true that they both use bcrypt to hash the password, however Devise adds a "pepper" to the password. You would have to add code to omniauth-identity to support a "pepper".
OmniAuth-Identity - lib/omniauth/identity/secure_password.rb
Devise - lib/devise/models/database_authenticatable.rb
Pepper your passwords
Devise adds a pepper to your passwords (since it is already salted by bcrypt), so in order for you to migrate devise users to omniauth-identity, you have to teach the identity strategy how to pepper passwords. This snippit works for us, however we didn't change the :stretches configuration option in devise.
# snatch the pepper setting from the devise initializer
pepper = "biglonguglystringfromyourdeviseinitializer"
# password created by devise in your db (i.e. "my_password_123")
encrypted_password = "$2a$10$iU.Br8ZClxuqldJt8Evl5OaBbHPJeBWbGV/1RoUsaNIZMBo8wHYTq"
# pepper the password then compare it using BCrypt like identity does
BCrypt::Password.new(encrypted_password) == "my_password_123#{pepper}"
=> true
How we made it work
This is a very quick and dirty monkey patch we used to make it work. We are investigating how to add this functionality in a more appropriate manner.
# file: ~/railsapp/config/initializers/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :identity
end
module OmniAuth
module Identity
module SecurePassword
module InstanceMethodsOnActivation
# Returns self if the password is correct, otherwise false.
def authenticate(unencrypted_password)
pepper = "the big pepper string from the devise initializer"
if BCrypt::Password.new(password_digest) == "#{unencrypted_password}#{pepper}"
self
else
false
end
end
# Encrypts the password into the password_digest attribute.
def password=(unencrypted_password)
pepper = "the big pepper string from the devise initializer"
#password = unencrypted_password
unless unencrypted_password.empty?
self.password_digest = BCrypt::Password.create("#{unencrypted_password}#{pepper}")
end
end
end
end
end
end

I dont think you are missing anything crucial at all. Both are using bcrypt. Awesome.
But does that matter ? I think it really does.
Devise completes your authentication process w.r.t to a Model i.e. say users in this case.
Now your old users are already registered and confirmed, so that really wouldn't be an issue. The encrypted_password and hashed_password are still in the users table for the users to access.
What do you have to worry about ?
=> You probably have to worry about the authenticate_user! filter. I am guessing since they both use bcrypt the authentication wouldn't really be an issue. Perhaps if OmniAuth isn't, they the way it authenticates users would be completely different and your code will break apart.
Thats the only thing you will have to look after is what i feel.

Related

Ruby on Rails - devise gem - How to retrieve decrypted password (plain text) from users table [duplicate]

I need to decrypt a password generated by devise.
For example, my password is test123test. devise generated this password:
$2a$10$vGeVVu.E0XGjlNEa0xMCK.R0SEH0aFuyJpefrq01Axz6WSbHApPEu
I need to decrypt the password and send test123test.
You can't, that's the whole point.
Bcrypt will allow you compare test123test with $2a$10$vGeVVu.E0XGjlNEa0xMCK.R0SEH0aFuyJpefrq01Axz6WSbHApPEu, but it will never give you the plain text password back. You might want to ask how to crack a bcrypt encrypted password instead (Very hard! Nearly impossible I think)
Jose Valim describes the motivation behind choosing bcrypt by linking to http://codahale.com/how-to-safely-store-a-password/ from the devise Google Group.
Use the recoverable module in Devise to reset the user's password.
devise :database_authenticatable, :registerable, :token_authenticatable,
:recoverable, :timeoutable, :trackable, :validatable, :rememberable
Devise will generate a password reset form and will send the user an email with the password reset link. The user clicks on the link, resets their password and signs in again.
What Leito said is right. You cannot get plain text password back or may take long long time to find. One other thing is you can check whether given password equals to encrypted one by bcrypt-calculator.
bcrypt-calculator
a.Look for BCrypt Tester
b.enter the password you want to check ex : test123test
c.enter the devise encrypted password ex : $2a$10$vGeVVu.E0XGjlNEa0xMCK.R0SEH0aFuyJpefrq01Axz6WSbHApPEu
press calculate.To find Password and hash match
Try this
I have passed params like
<ActionController::Parameters {"data"=>{"attributes"=>{"email"=>"venkateshkarni+u47#gmail.com", "old_password"=>"pass", "activated"=>"true", "password"=>"pass", "password_confirmation"=>"pass"}}, "controller"=>"account_block/accounts", "action"=>"change_password", "account"=>{}} permitted: false>
account_params = jsonapi_deserialize(params)
#user_account = User.find_by(email: account_params["email"])
#encrypt = BCrypt::Password.new(#user_account.password_digest)
if #encrypt == account_params["old_password"]
update_pass = #user_account.update(password: account_params['password'])
return render json: {message: 'You have successfully change password.'}, status: :ok
else
return render json: {errors: [
{account: "You've entered incorrect password."},
]}, status: :unprocessable_entity
end

AES Decryption in ruby and activerecord

I have super ugly code that looks like this:
class User < ActiveRecord::Base
self.table_name = 'users'
def get_password
#test_password = User.find_by_sql "SELECT CAST(AES_DECRYPT(Pass, 'kkk') AS CHAR(50)) Pass From prod.sys_users Where Owner = '"+#owner+"' AND User = '"+#user+"'"
#test_password[0].Pass
end
end
This code works, but it makes me sick, since it is not written according to Ruby Coding Style. So I decided to fix this code, and here what I have so far:
class User < ActiveRecord::Base
self.table_name = 'users'
def get_pass
User.where(Owner: #owner, User: #user).pluck(:Pass).first
end
end
So, I am getting encrypted password, how can I decrypt it?
I tired OpenSSL, but key 'kkk' here is too short.
How can I resolve this issue?
In a situation like this, you might be better off converting the field values entirely. This could be done in a migration and once it's done, you never have to be concerned about how MySQL has stored the data. It's also one step toward database independence.
So, the migration would basically do 3 things:
add a flag column to track which records have been converted
iterate over each records, converting the encrypted value and setting the flag
remove the flag column once all records have been processed
The migration might look like this:
class ConvertMySqlEncryptedData < ActiveRecord::Migration
# Local proxy class to prevent interaction issues with the real User class
class User < ActiveRecord::Base
end
def up
# Check to see if the flag has already been created (indicates that migration may have failed midway through)
unless column_exists?(:users, :encrypted_field_converted)
# Add the flag field to the table
change_table :users do |t|
t.boolean :encrypted_field_converted, null: false, default: false
end
end
# Add an index to make the update step go much more quickly
add_index :users, :encrypted_field_converted, unique: false
# Make sure that ActiveRecord can see the new column
User.reset_column_information
# Setup for AES 256 bit cipher-block chaining symetric encryption
alg = "AES-256-CBC"
digest = Digest::SHA256.new
digest.update("symetric key")
key = digest.digest
iv = OpenSSL::Cipher::Cipher.new(alg).random_iv
key64 = Base64.encode(key)
# Don't update timestamps
ActiveRecord::Base.record_timestamps = false
begin
# Cycle through the users that haven't yet been updated
User.where(encrypted_field_converted: false).pluck("CAST(AES_DECRYPT(Pass, 'kkk') AS CHAR(50)) Pass").each do |user|
# Re-encode the password with OpenSSL AES, based on the setup above
new_pass = aes.update(user.pass).final
# Update the password on the row, and set the flag to indicate that conversion has occurred
user.update_attributes(pass: new_pass, encrypted_field_converted: true)
end
ensure
# Reset timestamp recording
ActiveRecord::Base.record_timestamps = true
end
end
def down
# To undo or not undo, that is the question...
end
end
This was off the top of my head, so there may be issues with the encryption. Structure-wise, it should be in good shape, and it takes into account a number of things:
Provides incremental database processing by using a flag to indicate progress
Uses an index on the flag field to improve query performance, particularly if multiple runs are required to complete processing
Avoids updating the updated_at column to prevent overwriting prior values that may be useful to keep (this is not a material change, so updated_at doesn't require updating)
Plucks only the pass field, so that transfer overhead is minimized
Now, you can query pass and encrypt/decrypt as needed by the application. You can document and support the field at the application level, rather than rely on the database implementation.
I spent a few years consulting and doing database conversion, either from one database product to another, or as part of a significant version upgrade. It also allows development to use lighter-weight databases (e.g. SQLite) or test viability with other products when upscaling is needed. Avoiding database-specific features, like the MySQL encryption, will save you (or your employer) a LOT of money and hassle in the long run. Database independence is your friend; embrace it and use what ActiveRecord provides to you.

checking groups at runtime with devise and devise_ldap_authenticatable

I can get this devise_ldap_authenticatable working just fine when I don't care about what groups they are, it either connects to ldap and authenticates the user signing in under devise or doesn't. But I want to let only certain members that are apart of one or several specific groups in. I had a post on this question here:
Checking group membership in rails devise ldap gem, is it in the yaml?
(the gem for completeness sake is this one: https://github.com/cschiewek/devise_ldap_authenticatable)
Got to thinking I am asking the wrong question. I think I want to know how in devise (and the devise_ldap_authenticatable is the data stored where perhaps I can peek at my array of memberOf's myself and check the groups for myself in code, and then at that time don't let them in. Is there anywhere on the net that's hows this? My googling has turned up nothing but not being a ldap or devise pro I am guessing my terms suck.
I am sure I just might of missed the how to do this, closest I can see that might help (Though in its form as I read it makes little sense to me is the part on the readme here:
https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/README.md
about querying ldap, is this the case?)
You could do this with a callback or validation on the User (or equivalent) model.
before_create :user_is_not_member_of_specified_group?
private
def user_is_not_member_of_specified_group?
member_of = Devise::LdapAdapter.get_ldap_param(self.username,"memberOf")
test member_of
end
where test is a method that returns true/false based on your conditions for the member groups.
The Devise::LdapAdapter.get_ldap_param(self.username,"memberOf") is a method from devise_ldap_authenticatable that will return an array of member groups. You'll want to run your group testing on this array.
If you use a validation you could specify an error message for users that failed the test. Hope this helps.
EDIT
Another way to handle this would be to let your gem handle the redirection and error messages by monkeypatching the authorized? method in Devise::LdapAdapter::LdapConnect (https://github.com/cschiewek/devise_ldap_authenticatable/blob/master/lib/devise_ldap_authenticatable/ldap_adapter.rb). It would look like:
Devise::LdapAdapter::LdapConnect.class_eval do
def user_group_test
member_of = self.ldap_param_value("memberOf")
test member_of # your group test method
end
def authorized?
DeviseLdapAuthenticatable::Logger.send("Authorizing user #{dn}")
if !user_group_test
DeviseLdapAuthenticatable::Logger.send("Not authorized because custom authentication failed.")
return false
elsif !authenticated?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not authenticated.")
return false
elsif !in_required_groups?
DeviseLdapAuthenticatable::Logger.send("Not authorized because not in required groups.")
return false
elsif !has_required_attribute?
DeviseLdapAuthenticatable::Logger.send("Not authorized because does not have required attribute.")
return false
else
return true
end
end
end
You would want to put this in a custom initializer file in config/initializers.

Can I execute custom actions after successful sign in with Devise?

I have an app that has basic Devise authentication. After sign in, I would like to look up the user account (user belongs_to account, account has_many users), and store that in the session so that it is available like the #current_user.
What is the rails way of storing session in formation like this?
Is there a hook I can use with Devise to execute code after successful sign-in?
Actually, the accepted answer does not work properly in case of combined Omniauth and Database login modules in Devise.
The native hook that is executed after every successfull sign in action in Devise (disregarding the user authentication channel) is warden.set_user (called by devise sign_in helper: http://www.rubydoc.info/github/plataformatec/devise/Devise/Controllers/SignInOut#sign_in-instance_method).
In order to execute custom action after successfull user sign in (according to Warden Docs: https://github.com/hassox/warden/wiki/Callbacks), put this into initializer (eg. after_sign_in.rb in config/initializers)
Warden::Manager.after_set_user except: :fetch do |user, auth, opts|
#your custom code
end
Update 2015-04-30: Thanks to #seanlinsley suggestion (see comments below), I have corrected the answer to include except: :fetch in order to trigger the callback only when user is authenticated and not every time it is set.
Update 2018-12-27 Thanks to #thesecretmaster for pointing out that Warden now has built-in callbacks for executing your own code on after_authentication https://github.com/wardencommunity/warden/wiki/Callbacks#after_authentication
Edit: Please consider that this was once a good solution, but there are probably better ways of handling this. I am only leaving it here to give people another option and to preserve history, please do not downvote.
Yes, you can do this. The first resource I'd look at is http://github.com/plataformatec/devise/wiki/How-To:-Redirect-to-a-specific-page-on-successful-sign-in. Also, check out How to redirect to a specific page on successful sign up using rails devise gem? for some ideas.
You can do something like:
def after_sign_in_path_for(resource_or_scope)
session[:my_account] = current_user.account
profile_url
end
You can implement this method in your ApplicationController or in a custom RegistrationsController.
i'm using rails 5 and devise 4.2.1, my solution is overide devise function on user model:
def after_database_authentication
# here's the custom code
end
and the user model will look like this:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:timeoutable, :lockable
def after_database_authentication
# here's the custom code
end
end
it was called just after the authentication,
i read it from this devise documentation, hope this could help
I resolved this problem by overriding the create method of the session controller like following
class Admin::SessionsController < Devise::SessionsController
def create
super
# here goes my code
# my settings, etc
# do something with current_admin.fullname, for example
end
end
In other words, if authentication is successful (by calling super) then I perform my settings.
In application controller, you can simply add an after action.
app/controllers/users/application_controller.rb
class ApplicationController < ActionController::Base
after_action :do_something
def do_something
# do something
end
end

What is a very simple authentication scheme for Sinatra/Rack

I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.
In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.
I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.
Here is a very simple authentication scheme for Sinatra.
I’ll explain how it works below.
class App < Sinatra::Base
set :sessions => true
register do
def auth (type)
condition do
redirect "/login" unless send("is_#{type}?")
end
end
end
helpers do
def is_user?
#user != nil
end
end
before do
#user = User.get(session[:user_id])
end
get "/" do
"Hello, anonymous."
end
get "/protected", :auth => :user do
"Hello, #{#user.name}."
end
post "/login" do
session[:user_id] = User.authenticate(params).id
end
get "/logout" do
session[:user_id] = nil
end
end
For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.
The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)
Finally, the before handler sets up a #user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.
Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic, "Restricted Area" do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"You're welcome"
end
I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.
I' have found this tutorial and repository with a full example, its working fine for me
https://sklise.com/2013/03/08/sinatra-warden-auth/
https://github.com/sklise/sinatra-warden-example
I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:
use Rack::Session::Cookie, :key => 'rack.session',
:domain => 'foo.com',
:path => '/',
:expire_after => 2592000, # In seconds
:secret => 'change_me'
As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.
I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.
Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/

Resources