45

I've been attempting to hook a Rails application up to ActiveDirectory. I'll be synchronizing data about users between AD and a database, currently MySQL (but may turn into SQL Server or PostgreSQL).

I've checked out activedirectory-ruby, and it looks really buggy (for a 1.0 release!?). It wraps Net::LDAP, so I tried using that instead, but it's really close to the actual syntax of LDAP, and I enjoyed the abstraction of ActiveDirectory-Ruby because of its ActiveRecord-like syntax.

Is there an elegant ORM-type tool for a directory server? Better yet, if there were some kind of scaffolding tool for LDAP (CRUD for users, groups, organizational units, and so on). Then I could quickly integrate that with my existing authentication code though Authlogic, and keep all of the data synchronized.

GEOCHET
  • 21,119
  • 15
  • 74
  • 98
Clinton
  • 2,296
  • 4
  • 19
  • 21

6 Answers6

41

Here is sample code I use with the net-ldap gem to verify user logins from the ActiveDirectory server at my work:

require 'net/ldap' # gem install net-ldap

def name_for_login( email, password )
  email = email[/\A\w+/].downcase  # Throw out the domain, if it was there
  email << "@mycompany.com"        # I only check people in my company
  ldap = Net::LDAP.new(
    host: 'ldap.mycompany.com',    # Thankfully this is a standard name
    auth: { method: :simple, email: email, password:password }
  )
  if ldap.bind
    # Yay, the login credentials were valid!
    # Get the user's full name and return it
    ldap.search(
      base:         "OU=Users,OU=Accounts,DC=mycompany,DC=com",
      filter:       Net::LDAP::Filter.eq( "mail", email ),
      attributes:   %w[ displayName ],
      return_result:true
    ).first.displayName.first
  end
end

The first.displayName.first code at the end looks a little goofy, and so might benefit from some explanation:

  • Net::LDAP#search always returns an array of results, even if you end up matching only one entry. The first call to first finds the first (and presumably only) entry that matched the email address.

  • The Net::LDAP::Entry returned by the search conveniently lets you access attributes via method name, so some_entry.displayName is the same as some_entry['displayName'].

  • Every attribute in a Net::LDAP::Entry is always an array of values, even when only one value is present. Although it might be silly to have a user with multiple "displayName" values, LDAP's generic nature means that it's possible. The final first invocation turns the array-of-one-string into just the string for the user's full name.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • Thanks for the late answer. I hardly need this information anymore, but this syntax looks stellar, and MUCH shorter than the way I was attempting to do it. Thanks again! – Clinton Apr 18 '11 at 04:34
  • 1
    Thanks for posting this. I had been hitting a wall simply because I wasn't including the @company.com for the user name. Your post got me going in the proper direction. – Chris Mendla May 02 '16 at 12:59
8

Have you tried looking at these:

http://saush.wordpress.com/2006/07/18/rubyrails-user-authentication-with-microsoft-active-directory/

http://xaop.com/blog/2008/06/17/simple-windows-active-directory-ldap-authentication-with-rails/

mrTomahawk
  • 944
  • 1
  • 7
  • 14
  • 5
    mrT - Many of the links that presumably once worked in your answer are now broken. Could you be persuaded to update them? Thanks in advance. – Chris Markle Jan 08 '10 at 20:22
  • 1
    new how to authenticate with ldap link: http://wiki.rubyonrails.org/rails/pages/howtoauthenticatewithrubynetldap – Charles Ma Oct 15 '10 at 13:24
4

This is more anecdotal than a real answer...

I had a similar experience using Samba and OpenLDAP server. I couldn't find a library to really do what I wanted so I rolled my own helper classes.

I used ldapbrowser to see what fields Samba filled in when I created a user the "official" way and and basically duplicated that.

The only tricky/non-standard LDAP thing was the crazy password encryption we have:

userPass:

"{MD5}" + Base64.encode64(Digest::MD5.digest(pass))

sambaNTPassword:

OpenSSL::Digest::MD4.hexdigest(Iconv.iconv("UCS-2", "UTF-8", pass).join).upcase

For the def authenticate(user, pass) function I try to get LDAP to bind to the domain using their credentials, if I catch an exception then the login failed, otherwise let them in.

Lolindrath
  • 2,101
  • 14
  • 20
4

Sorry, cannot comment yet... perhaps someone can relocate this appropriately.

@Phrogz's solution works well, but bind_simple (inside bind) raises an Net::LDAP::LdapError exception due to auth[:username] not being set as shown here:

https://github.com/ruby-ldap/ruby-net-ldap/blob/master/lib/net/ldap.rb

The corrected replaces:

auth: { method: :simple, email: email, password:password }

with:

auth: { method: :simple, username: email, password:password }
jordanpg
  • 6,386
  • 4
  • 46
  • 70
  • Incidentally, you can edit other people's posts -- the edits will go into the Suggested Edits queue, where two people can confirm or deny the edit is a good one. :) – sarnold Feb 18 '12 at 02:38
2

I began using ruby-activedirectory, and even extended it/fixed a few things, hosting judy-activedirectory in Github.

Doing the next iteration, I've discovered ActiveLdap has a much better code base, and I'm seriously contemplating switching to it. Does anyone have personal experience with this?

Clinton
  • 2,296
  • 4
  • 19
  • 21
1

Have you checked out thoughtbot's ldap-activerecord-gateway? It might be something for you to consider...

http://github.com/thoughtbot/ldap-activerecord-gateway/tree/master

  • Hmm. I'm still trying to understand what this could do for us. Essentially, I would start this ldap server when starting the rails application. Then, if there's a way to replicate data between this and the real AD server, do that. Then use my ldap server for data. Does that make sense? – Clinton Dec 03 '08 at 03:38
  • this repo is not existent anymore. – Piezo Pea Jun 14 '23 at 13:45