Last active
January 5, 2024 07:11
-
-
Save jeffjohnson9046/7012167 to your computer and use it in GitHub Desktop.
Some VERY basic LDAP interaction in Ruby using Net::LDAP.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
####################################################################################################################### | |
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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:
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.