Rails and LDAP gotchas

February 28, 2007 Link to post  Permalink

I’ve spent some time over the last few days trying to access a Microsoft Active Directory (AD) using LDAP from a Rails app. Although there are some libraries and a few blog posts, this is still a very painful thing to do.

Here’s some things I worked through so you don’t have to.

LDAP paths are location dependent, but not case sensitive

Not a Ruby or Rails issue, but something you need to know – “dc=foo,dc=com” is not the same as “dc=com,dc=foo”. However, DC=Foo,DC=com is the same as dc=foo,dc=com

Pre-built Windows ruby-ldap needs no other LDAP libraries

Chris Scharf has built the ruby ldap libraries for Windows Although the ruby-ldap site says the code relies on other libraries, on Windows, that code is built in.

Here’s some ruby-ldap code that works with my AD server:

 1equire 'ldap'
 2
 3conn = LDAP::Conn.new( '<domain-server>', 389 )
 4conn.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 )
 5conn.bind( '<domain>\<username>', '<password>' ) do |conn|
 6
 7  base = 'ou=Users,ou=<container>,dc=<domain>,dc=local'
 8
 9  results = conn.search2(base, LDAP::LDAP_SCOPE_SUBTREE, '(cn=*)')
10  results.each { |entry| puts "#{entry['dn']}: #{entry['telephoneNumber']}" }
11
12end

Note: I’m using a Small Business Server, and the container for me is MyBusiness. Also, my full domain name is .local.

Most ActiveLDAP document is wrong

ActiveLDAP is getting closer to ActiveRecord in the way it works, and a bunch of the initialization code has changed to do this, but the documentation is not entirely correct. I have version 0.8 and here’s what I know:

  • Use ActiveLdap::Base.establish_connection to setup the connection
  • :password can now be used instead of :password_block
  • :user isn’t yet used (but is documented) – use :bind_as instead

Here’s my code from environment.rb:

 1equire 'active_ldap'
 2ActiveLdap::Base.establish_connection(
 3  :host => '<domain-server>',
 4  :port => 389,
 5  :base => 'dc=<domain-name>,dc=local',
 6  :bind_format => '%s',
 7  :bind_dn => '<domain-name>\\<username>',
 8  :password_block => Proc.new { '<password>' },
 9  :allow_anonymous => false
10)

ActiveLDAP has some bugs in a Rails app

There are some bugs in ActiveLDAP 0.8 that I had to work around

Implement to_param

If you implement in your model code:

1ef to_param
2  id
3end

Then the Rails scaffold code will work great!

Schema processing broke for me

At line 44 of ActiveLDAP’s schema.rb file, I had to update to this to stop the code crashing:

1     while @entries[group] && schema = @entries[group].shift

The DN value for saving is broken

I poked around and commented out some code, but I really didn’t understand what I was doing! Part of my confusion is that ActiveLdap::Base has two implementations of the base method that interact weirdly. The problem is that the base part of the DN was being added twice, and the DN attribute name (in my case CN) was on the front of the string. Here’s an example of what I was seeing:

1n=cn=<my name>,ou=Users,ou=MyBusiness,dc=<domain>,dc=local,ou=Users,ou=MyBusiness,dc=<domain>,dc=local

The string that did work was:

1n=<my name>,ou=Users,ou=MyBusiness,dc=<domain>,dc=local

nTSecurityDescriptor isn’t supported

After getting past the DN string building, this was my next problem with save (I actually disabled validation to get here, as the validation does insist this is needed)

Ok, so this is mostly the fault of AD, but if you want to save changes back to AD, then you need to be able to get the nTSecurityDescription attribute for your objects. It seems that AD has implemented an extension to retrieve this attribute, but I have no idea how to do that.

What else can go wrong?

I was planning on being able to build a live search against my users in the AD using LDAP, but the performance sucks, even for only a dozen users, it takes over a second on my unloaded server with everything in memory.