####################################################################################################################### | |
# This Gist is some crib notes/tests/practice/whatever for talking to Active Directory via LDAP. The (surprisingly | |
# helpful) documentation for Net::LDAP can be found here: http://net-ldap.rubyforge.org/Net/LDAP.html | |
####################################################################################################################### | |
require 'rubygems' | |
require 'net/ldap' | |
####################################################################################################################### | |
# HELPER/UTILITY METHOD | |
# This method interprets the response/return code from an LDAP bind operation (bind, search, add, modify, rename, | |
# delete). This method isn't necessarily complete, but it's a good starting point for handling the response codes | |
# from an LDAP bind operation. | |
# | |
# Additional details for the get_operation_result method can be found here: | |
# http://net-ldap.rubyforge.org/Net/LDAP.html#method-i-get_operation_result | |
####################################################################################################################### | |
def get_ldap_response(ldap) | |
msg = "Response Code: #{ ldap.get_operation_result.code }, Message: #{ ldap.get_operation_result.message }" | |
raise msg unless ldap.get_operation_result.code == 0 | |
end | |
####################################################################################################################### | |
# SET UP LDAP CONNECTION | |
# Setting up a connection to the LDAP server using .new() does not actually send any network traffic to the LDAP | |
# server. When you call an operation on ldap (e.g. add or search), .bind is called implicitly. *That's* when the | |
# connection is made to the LDAP server. This means that each operation called on the ldap object will create its own | |
# network connection to the LDAP server. | |
####################################################################################################################### | |
ldap = Net::LDAP.new :host => # your LDAP host name or IP goes here, | |
:port => # your LDAP host port goes here, | |
:encryption => :simple_tls, | |
:base => # the base of your AD tree goes here, | |
:auth => { | |
:method => :simple, | |
:username => # a user w/sufficient privileges to read from AD goes here, | |
:password => # the user's password goes here | |
} | |
####################################################################################################################### | |
# ALTERNATIVE FOR OPENING LDAP CONNECTION | |
# Instead of using .new, you can call .open. Within .open's code block, you can perform whatever LDAP operations you | |
# need in the context of a single network connection. | |
####################################################################################################################### | |
host = # your LDAP host name or IP goes here | |
port = # your LDAP host port goes here | |
base = # the base of your AD tree goes here | |
credentials = { | |
:method => :simple, | |
:username => # a user w/sufficient privileges to read from AD goes here, | |
:password => # the user's password goes here | |
} | |
Net::LDAP.open(:host => host, :port => port, :encryption => :simple_tls, :base => base, :auth => credentials) do |ldap| | |
# Do all your LDAP stuff here... | |
end | |
####################################################################################################################### | |
# SOME SIMPLE LDAP SEARCHES | |
####################################################################################################################### | |
# GET THE DISPLAY NAME AND E-MAIL ADDRESS FOR A SINGLE USER | |
search_param = # the AD account goes here | |
result_attrs = ["sAMAccountName", "displayName", "mail"] # Whatever you want to bring back in your result set goes here | |
# Build filter | |
search_filter = Net::LDAP::Filter.eq("sAMAccountName", search_param) | |
# Execute search | |
ldap.search(:filter => search_filter, :attributes => result_attrs) { |item| | |
puts "#{item.sAMAccountName.first}: #{item.displayName.first} (#{item.mail.first})" | |
} | |
get_ldap_response(ldap) | |
# --------------------------------------------------------------------------------------------------------------------- | |
# GET THE DISPLAY NAME AND E-MAIL ADDRESS FOR AN E-MAIL DISTRIBUTION LIST | |
search_param = # the name of the distribution list you're looking for goes here | |
result_attrs = ["sAMAccountName", "displayName", "mail"] # Whatever you want to bring back in your result set goes here | |
# Build filter | |
search_filter = Net::LDAP::Filter.eq("sAMAccountName", search_param) | |
group_filter = Net::LDAP::Filter.eq("objectClass", "group") | |
composite_filter = Net::LDAP::Filter.join(search_filter, group_filter) | |
# Execute search | |
ldap.search(:filter => composite_filter, :attributes => result_attrs) { |item| | |
puts "#{item.sAMAccountName.first}: #{item.displayName.first} (#{item.mail.first})" | |
} | |
get_ldap_response(ldap) | |
# --------------------------------------------------------------------------------------------------------------------- | |
# GET THE MEMBERS OF AN E-MAIL DISTRIBUTION LIST | |
search_param = # the name of the distribution list you're looking for goes here | |
result_attrs = ["sAMAccountName", "displayName", "mail", "member"] | |
# Build filter | |
search_filter = Net::LDAP::Filter.eq("sAMAccountName", search_param) | |
group_filter = Net::LDAP::Filter.eq("objectClass", "group") | |
composite_filter = Net::LDAP::Filter.join(search_filter, group_filter) | |
# Execute search, extracting the AD account name from each member of the distribution list | |
ldap.search(:filter => composite_filter, :attributes => result_attrs) do |item| | |
puts "#{item.sAMAccountName.first}: #{item.displayName.first} (#{item.mail.first})" | |
item.member.map { |m| puts "\taccount: #{m.match(/(?<=\().+?(?=\))/)}" } | |
end | |
get_ldap_response(ldap) | |
# --------------------------------------------------------------------------------------------------------------------- | |
# GET THE DISPLAY NAME AND E-MAIL ADDRESS FOR ALL E-MAIL DISTRIBUTION LISTS | |
# Build filter | |
# This stackoverflow article was a HUGE help: http://stackoverflow.com/questions/6434752/better-way-to-query-an-ldap-users-via-ruby-net-ldap | |
group_filter = Net::LDAP::Filter.eq("objectClass", "group") | |
proxy_address_filter = Net::LDAP::Filter.eq("proxyAddresses", "*") | |
composite_filter = Net::LDAP::Filter.join(group_filter, proxy_address_filter) | |
# Execute search | |
ldap.search(:filter => composite_filter, :attributes => result_attrs) { |item| | |
puts "#{item.sAMAccountName.first}: #{item.mail.first}" | |
} | |
get_ldap_response(ldap) | |
####################################################################################################################### | |
# LDAP FILTER EXAMPLES | |
####################################################################################################################### | |
# CONSTRUCT AN OR FILTER WITH SEVERAL EQUALS | |
# If you come across a situation where you need to search LDAP for this == x | this == y | this == z, there isn't an | |
# easy way to deal with it. The Filter.intersect method takes two arguments, but that isn't enough (because we have | |
# 3 "OR" conditions). Fortunately, Net::LDAP::Filter has a .construct method that will build a valid query string for | |
# us (with a little help): | |
names = ["lstarr", "barf", "dmatrix", "pvespa", "yogurt"] | |
filters = names.map { |name| Net::LDAP::Filter.eq("sAMAccountName", name) } | |
search_filter = Net::LDAP::Filter.construct("(|#{ filters.join("") })") | |
# search_filter => (|(|(|(|(sAMAccountName=lstarr)(sAMAccountName=barf))(sAMAccountName=dmatrix))(sAMAccountName=pvespa))(sAMAccountName=yogurt)) | |
# Ugly, probabaly inefficient, but it'll work. Now we can do this: | |
emails = [] | |
ldap.search(:filter => search_filter, :attributes => ["mail"], :return_result => false) do |result| | |
emails << (result.mail.is_a?(Array) ? result.mail.first : result.mail) | |
end |
This comment has been minimized.
This comment has been minimized.
Struggled with this for a while but finally got it work. In my case I had to set :base="com.xyz.intranet" where com.xyz.intranet is the root of the ldap directory tree that I had read access. If you do not know the root (base) level in the ldap (or active) directory tree that you have read access to, I would suggest you install a ldap browser like "JXplorer - an open source LDAP browser" and find out what your "base" value should be. Play with JXplorer for 15 minutes and your should be able to find the appropriate "base" value for your ldap environment. Then use that value in your code and all of the above things should work fine... |
This comment has been minimized.
This comment has been minimized.
uids = ["cu2300", "aa9939"] emails = [] p ldap.get_operation_result This does not work. It should return some records, I am sure those users exist. |
This comment has been minimized.
This comment has been minimized.
With filtering, you can also use http://www.rubydoc.info/github/ruby-ldap/ruby-net-ldap/Net%2FLDAP%2FFilter.ne |
This comment has been minimized.
This comment has been minimized.
Is there a way to print out the actual commands being sent to LDAP? |
This comment has been minimized.
This comment has been minimized.
Hello , Thank you or that the code seems good to perform some request over ldap. Thank you. |
This comment has been minimized.
This comment has been minimized.
@dem972: I wouldn't put this type of code directly in a controller. In my opinion, controllers are supposed to be pretty "dumb", with just enough logic to unpack the request and route it to where it's supposed to go. I'd probably do something like this:
The basic idea here is that you're abstracting away the interaction with the LDAP server, so that you can easily swap it out for something else later down the line (e.g. maybe you get user info from a MySQL database instead of an LDAP server [for whatever reason]). I haven't done any Ruby in a long time, but in pseudocode it might look something like this: # LdapUserRepository.rb
require 'net/ldap'
class LdapUserRepository
def find_user_by_email(email_address)
# open a connection to the LDAP server and query for the user's email address
end
def find_group(group_name)
# open a connection to the LDAP server and query for a group of users
end
def find_by_some_attribute(attribute_value)
# open a connection to the LDAP server and find a collection of users based on some attribute
end
end
# LdapUserService.rb
class LdapUserService
def find_user(email_address)
user = nil
begin
# NOTE: This is probably not the right place to create a new instance of your LdapUserRepository. I just did
# it here for the sake of this example
ldapRepository = new LdapUserRepository
user = ldapRepository.find_user_by_email(email_address)
# do some other work with the user here
rescue
# do something meaningful with the exception
ensure
# clean up any resources if there's anything you need to do
end
return user
end
# other methods
end
# LdapUserController.rb
class UserController
# controller methods here
end All of this is merely a suggestion that might not fit your project. But this is the general approach I'd try to start with. |
This comment has been minimized.
You must add :base=>"" to your ldap.search calls in order to succeed