Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby on Rails 3: Devise::LdapAdapter.get_ldap_param undefined method error

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.

like image 896
whitemage Avatar asked Nov 15 '11 20:11

whitemage


1 Answers

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.

like image 190
svoisen Avatar answered Oct 05 '22 11:10

svoisen