The restful_authentication plugin by Rick Olson is THE defacto standard for authentication in rails apps. Nearly all mid-sized and small rails apps use it. It works right out of the box with MySQL is REST compliant, and has some really nice code and test cases (specs now acutally) to make sure everything is working. Rick does a good job of keeping it up to date as well and addresses security concerns of the community as they are brough up.
But what happens when you want to customize this plugin? I've made it work with rails version of "forms" authenticaiton with a SQL 05 server, that wasn't too hard. I've changed it to have personal questions per user account and require correct answers to those questions before sending an email to reset a user's password. All of these kinds of modifications are expected. The plugins is just there to get you started. So what do we do when we cant to take Rick's code and authenticate agains our internal user database stored in Active Directory? We extend his code yet again!
The hardest part of making this work was getting all the settings for authenticating to your specific LDAP server figured out. I suggest working with someone at your orginization that has first hand experience with your company's Active Directory setup and specifically how the LDAP is configured in your environment.
Below is the user.rb for a rails system that needs to authenticate to Active Directory. You need LDAP services turned on (usually this is the case by default). You will need to check with your AD administrator anyway to get all the connection information, DSN, CN, etc.
To get started you will need to install the 'ruby-net-ldap' gem. At your command prompt type:
sudo gem install ruby-net-ldap
See the documentation here: http://rubyfurnace.com/docs/ruby-net-ldap-0.0.4/
You'll want to start by reading the section on "Net::LDAP" (See link list on the left)
This code is a drop in replacement for the self.authenticate method in the restful_authentication plugin. After adding the line: require 'net/ldap' at the top, the only other change is to remove the existing self.authenticate method and replace it with the 2 shown in this post. Optionally, you can remove all the password code that is no longer in use. The exceptions are the "self.encrypt" method and the "encrypt" method which are still in use by the "remember_me_until" method.
#place at the top of the user.rb model
require 'net/ldap'
#LDAP AD Authentication (this method replaces self.authenticate for the restful_authentication plugin)
def self.authenticate(username,password)
#passing in the user with the dc attached... you should also be able to use the full CN
ldap_con = initialize_ldap_con(username + "@domain.com", password)
treebase = "DC=domain,DC=com"
user_filter = Net::LDAP::Filter.eq( "sAMAccountName", username )
#op_filter = Net::LDAP::Filter.eq( "objectClass", "organizationalPerson" ) #not used
#ldap will automatically bind when trying to preform a search or modification, so we don't call .bind here.
#.bind is only if you want to just return true or false, but we want to look up some attributes!
if ldap_con.search( :base => treebase,
:filter => user_filter,
:attributes=> ['dn','sAMAccountName','displayname','SN','givenName']) do |ad_user|
#try to find the user locally and if they aren't there then create them.
local_user = find_by_login(ad_user.samaccountname.to_s)
if !local_user
local_user = User.new(:login => ad_user.samaccountname.to_s,
:name => ad_user.displayname.to_s,
:email => ad_user.givenname.to_s + '.' + ad_user.sn.to_s + '@domain.com')
local_user.save false #pass false to skip validations
return local_user
else
#They were found in the local database but to keep the local user info sync'd
#each time we login we update the local db's name and email fields.
local_user.update_attributes(:name => ad_user.displayname.to_s,
:email => ad_user.givenname.to_s + '.' + ad_user.sn.to_s + '@domain.com')
local_user.save false
return local_user
end
end
else
return nil #they didn't authenticate to AD
end
end
#helper method called from above
def self.initialize_ldap_con(user_name, password)
ldap = Net::LDAP.new
ldap.host = "your_ldap_server_IP"
ldap.port = 636 #required for SSL connections, 389 is the default plain text port
ldap.encryption :simple_tls #also required to tell Net:LDAP that we want SSL
ldap.auth "#{user_name}","#{password}"
ldap #explicitly return the ldap connection object
end
blog