Erik's Engineering

something alliterative

ActiveDirectory Integration for Ruby apps

So you're at work, building an awesome rails app for your coworkers to use. Then someone says "can you make it so people can log in with their Windows credentials?" That means ActiveDirectory integration.

Before you code something up, you should check out a couple off the shelf options.

If you're on apache, mod_authz_ldap might do the trick. The problem there is that it's using http basic auth, which doesn't have a logout function.

I haven't tried it, but Rack::Auth::Kerberos might also do the trick.

I wanted a deeper integration with the app than it seemed like I was going to get from either of those, so I ended up rolling my own using the net-ldap gem.

Checking passwords

The first step is to check passwords. For this, you need to know the domain the user is logging in on and the ip and port of your ActiveDirectory server.

The act of connecting to the server (and checking credentials in the process) is called binding. You don't need a special account for it - you can just use the credentials your user gave you.

require 'net-ldap'

host =    "10.0.0.1"
port =    389     # default port
domain =  "MYDOMAIN"
 
ldap = Net::LDAP.new(:host => host, :port => port)
ldap.auth("#{self.username}@#{domain}", password)

# ldap.bind is false if username/password are bad
if ldap.bind
   #  logged in
else
   #  fail
end

Checking group membership

Too bad no one ever wants to give access to everyone on the domain. It seems like you always end up needing to restrict it some more. Besides, there's a really big win to be had from letting the sysadmins manage permissions via their normal A/D groups.

Unfortunately, this doesn't work out as cleanly as checking the password. You end up iterating over user data until you find the right items and then parsing those items to find the group names.

You also need to find out where in the ldap data tree your accounts are stored. This is going to be different everywhere - you'll need to talk to your sysadmins to find out. It should be something that looks like ou=Users,ou=Accounts,ou=This Office,dc=something,dc=orother,dc=com. The more sophisticated your A/D setup, the longer and more complicated the string.

Once you search for your user, you get back a bunch of different pieces of data about them. The one we care about is "memberof", which has a list of groups they belong to. Except they don't just list the names of the groups, they prefix them with CN= and maybe suffix them with some other stuff, so we still need to parse things a little more.

treebase = 'ou=Users,ou=Accounts,ou=This Office,dc=something,dc=orother,dc=com'
group =    'Uber Users'


# ldap.bind is false if username/password are bad
if ldap.bind
  filter = Net::LDAP::Filter.eq("sAMAccountName", self.username)

  # find the list of groups they belong to and iterate over it
  group_member = false
  ldap.search(:base => treebase, :filter => filter) do |entry|
    entry.each do |attribute, values|
      if attribute.match(/memberof/)
        values.each do |value|
          a = value.split(',')
          md = a[0].match(/CN=(.+)/)
         
          # user is a member of the right group
          if md[1] == group
            group_member = true
          end
        end
      end
    end
  end

  result = ldap.get_operation_result
  unless result.code == 0 
    self.errors.add_to_base("Error while checking group membership.")
  end

  unless result.code == 0  && group_member
    self.errors.add_to_base("Must be a member of the #{group} group.")
  end
end

Summary

With this technique, you can pretty easily authenticate your users without making them remember yet another username and password. Since you're always going right back to the source, there won't be any weird lags after password changes. You can make a list of their groups and use those for fine-grained access controls (I'd suggest storing them in the session).

Published on 22/05/2010 at 14h26 under . Tags , ,

Comment ActiveDirectory Integration for Ruby apps

Trackbacks are disabled

Powered by Typo – Thème Frédéric de Villamil | Photo Glenn