This is a follow up on my post made on 30th October 2011, Migrating Monolith Rails 2.x Apps. I’m going to walk through the basic steps necessary to make rubycas work with existing Devise authentication data, more specific Devise v1.0.11.
To enable rubycas-server
to work with your existing user data we are going to create a custom authenticator:
# encoding: UTF-8
require 'casserver/authenticators/sql'
require 'devise/encryptors/base'
require 'devise/encryptors/sha1'
class CustomAuthenticator < CASServer::Authenticators::SQL
# snip from devise lib
def secure_compare(a, b)
return false unless a.present? && b.present?
return false unless a.bytesize == b.bytesize
l = a.unpack "C#{a.bytesize}"
res = 0
b.each_byte { |byte| res |= byte ^ l.shift }
res == 0
end
# copied from devise.rb initializer
DEVISE_STRETCHES = 7
DEVISE_PEPPER = 'my-devise-pepper'
def password_digest(password, password_salt)
Devise::Encryptors::Sha1.digest(password, DEVISE_STRETCHES, password_salt, DEVISE_PEPPER)
end
def valid_for_authentication?(user, incoming_password)
secure_compare(password_digest(incoming_password,user.password_salt), user.encrypted_password)
end
def validate(credentials)
read_standard_credentials(credentials)
raise_if_not_configured
user_model = self.class.user_model
username_column = @options[:username_column] || "username"
$LOG.debug "#{self.class}: [#{user_model}] " + "Connection pool size: #{user_model.connection_pool.instance_variable_get(:@checked_out).length}/#{user_model.connection_pool.instance_variable_get(:@connections).length}"
results = user_model.find(:all, :conditions => ["#{username_column} = ?", @username])
user_model.connection_pool.checkin(user_model.connection)
if results.size > 0
$LOG.warn("Multiple matches found for user '#{@username}'") if results.size > 1
user = results.first
unless @options[:extra_attributes].blank?
if results.size > 1
$LOG.warn("#{self.class}: Unable to extract extra_attributes because multiple matches were found for #{@username.inspect}")
else
extract_extra(user)
log_extra
end
end
return valid_for_authentication? user, @password
else
return false
end
end
end
All I did above is to extract the valid_for_authentication?
method from Devise 1.0.11 into a rubycas authentication class.
Ideally we should keep the pepper and stretches configuration in a separate file. Requiring your devise.rb
initializer however does not work for Devise 1.0.11 because it assumes you are running a Ruby on Rails application which is not the case here.
I’d probably put it into a Yaml file and load it on demand - the implementation for that is simple so I skipped it here.
Please note that the above example assumes you configured Devise to use the SHA1
hashing algorithm. An example snippet from your devise.rb
initializer could look like this:
Devise.setup do |config|
# snip
config.pepper = "my-devise-pepper"
config.stretches = 7
config.encryptor = :sha1
# snip
end
Now all you need to do to use your custom authenticator is to update your config.yml:
authenticator:
class: CustomAuthenticator
database:
adapter: mysql
database: rails_app_database
username: username
password: password
host: localhost
user_table: users
username_column: email
and to restart your rubycas server. You should now be able to log-in using your existing user data.
Note
The changes necessary to support more recent versions of Devise should be similar. I have not checked yet, but might do so in the future. I might update this post with another example for Devise 2.0.
Next Up:
Redirect all log-in requests on the old Rails App to use our newly setup rubycas-server