I am running: Ruby 1.9.3p0, Rails 3.1.1, Devise 1.4.9, Devise_ldap_authenticatable 0.4.10
I am using Devise to authenticate my Rails application via an ldap server. I am using username instead of email to authenticate, so naturally the email field in my table is blank.
To query the ldap for email, the official way is to add this code in the user model:
before_save :get_ldap_email
def get_ldap_email
self.email = Devise::LdapAdapter.get_ldap_param(self.username,"mail")
end
This code fails, without attempting to do anything with the ldap, with this:
undefined method `mail' for nil:NilClass
It refers to the line inside the method definition. The log output is no more helpful:
Started POST "/users/sign_in" for 10.50.1.96 at 2011-11-15 11:18:16 -0800
Processing by Devise::SessionsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"<hidden>=", "user"=>{"username"=>"<hidden>", "password"=>"[FILTERED]", "remember_me"=>"0"}, "commit"=>"Sign in"}
User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."username" = '<hidden>' LIMIT 1
LDAP: LDAP dn lookup: uid=<hidden>
LDAP: LDAP search for login: uid=<hidden>
LDAP: Authorizing user <hidden>
LDAP: LDAP dn lookup: uid=<hidden>
LDAP: LDAP search for login: <hidden>
Completed 500 Internal Server Error in 251ms
NoMethodError (undefined method `mail' for nil:NilClass):
app/models/user.rb:14:in `get_ldap_email'
All lines previous to the 500 error are normal LDAP successful authentication that are unrelated to the the email query.
I started learning Ruby, Rails, and Devise just last week, so I'm not sure what files would be the most telling, but here is my user.rb model and gemfile:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :ldap_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
before_save :get_ldap_email
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :username, :password, :password_confirmation, :remember_me
def get_ldap_email
self.email = Devise::LdapAdapter.get_ldap_param(self.username,"mail")
end
end
And the gemfile:
source 'http://rubygems.org'
gem 'rails', '3.1.1'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
<... unrelated ...>
gem 'therubyracer', :platforms => :ruby
gem "devise"
gem "devise_ldap_authenticatable"
I have tried restarting the server, and have done a bundle install since the last GemFile update. My configuration has ldap_create_user = true and username is the correct field name in users. Is there an error in that method? Could there be a version incompatibility? I'm not really sure what else to check, and rails is giving me nothing beginner-friendly to go on. I would love some help with this.
I'm also having this problem - my current temporary solution is to fetch the data myself using Net::LDAP
instead of the ldap_authenticatable
classes. The more permanent solution would, of course, be to patch ldap_authenticatable
, which I may try to do next.
The issue (at least for me) was this: After poring through the ldap_authenticatable
code (namely ldap_adapter.rb) I discovered that the get_ldap_param
method is not authenticating with the server when trying to fetch the params unless admin_user
and admin_password
are specified in ldap.yml. So, if your LDAP server allows anonymous reading of data, then get_ldap_param
will work as advertised. On OpenLDAP (which is what I use for local testing), anonymous read access is set with the "access to" property in slapd.conf:
access to *
by anonymous auth
But, my corporate LDAP does not allow that.
The Net::LDAP instance in ldap_authenticatable
needs to be created with auth parameters when being used for parameter fetching, but it's not. No auth parameters are given, so no results are returned.
So, temporarily, I have the following code in my User model, calling get_ldap_data
as a before_save filter:
def has_ldap_data?
[self.email, self.first_name, self.last_name].none? {|v| v.nil?}
end
def get_ldap_data
return true if has_ldap_data? or self.password.nil?
ldap = create_ldap
ldap.search(
:base => ldap.base,
:attributes => ['cn', 'sn', 'givenname', 'mail'],
:filter => Net::LDAP::Filter.eq('cn', self.username),
:return_result => true) do |entry|
self.email = entry.mail.first
self.first_name = entry.givenname.first
self.last_name = entry.sn.first
end
has_ldap_data?
end
def create_ldap(password = nil)
ldap_config = ldap_config = YAML.load(ERB.new(File.read(::Devise.ldap_config || "#{Rails.root}/config/ldap.yml")).result)[Rails.env]
ldap_options = {
:host => ldap_config["host"],
:port => ldap_config["port"],
:base => ldap_config["base"],
:auth => {
:method => :simple,
:username => "cn=#{self.username},#{ldap_config['base']}",
:password => password || self.password
}
}
ldap_config["ssl"] = :simple_tls if ldap_config["ssl"] === true
ldap_options[:encryption] = ldap_config["ssl"].to_sym if ldap_config["ssl"]
Net::LDAP.new(ldap_options)
end
Modify per your particular needs. It's not ideal, but works for now until forking/patching ldap_authenticatable
to account for this use case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With