Skip to content

Instantly share code, notes, and snippets.

@dnozay
Created March 7, 2012 22:35
Show Gist options
  • Save dnozay/1996782 to your computer and use it in GitHub Desktop.
Save dnozay/1996782 to your computer and use it in GitHub Desktop.
ldap paged search
#!/usr/bin/python
# samples
# http://www.grotan.com/ldap/python-ldap-samples.html
# useraccountcontrol
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms680832(v=vs.85).aspx
# paging search
# http://www.novell.com/coolsolutions/tip/18274.html
import ldap
from ldap.controls import SimplePagedResultsControl
class LDAPConf:
'''
This contains all details that pertain to our ldap instance
'''
url = 'changeme'
# don't chase referrals since MSAD does not like it.
chase_referrals = 0
protocol_version = ldap.VERSION3
bind_dn = 'changeme'
bind_password = 'changeme'
base_dn = 'changeme'
search_scope = ldap.SCOPE_SUBTREE
# search all active accounts
search_filter = '(&(objectcategory=person)(objectclass=user)'\
'(!(|(useraccountcontrol:1.2.840.113556.1.4.803:=2)'\
'(useraccountcontrol:1.2.840.113556.1.4.803:=65536))))'
# retrieve all attributes
retrieve_attrs = None
# page search results
page_size = 100
# size limit
size_limit = 0
def connect():
'''
setup the connection.
This should take care of all details that pertain to our ldap instance
'''
global conn
conn = ldap.open(LDAPConf.url)
conn.set_option(ldap.OPT_REFERRALS, LDAPConf.chase_referrals)
conn.set_option(ldap.OPT_SIZELIMIT, LDAPConf.size_limit)
conn.simple_bind_s(LDAPConf.bind_dn, LDAPConf.bind_password)
conn.protocol_version = LDAPConf.protocol_version
def disconnect():
'''
close the connection.
This should take care of all details that pertain to our ldap instance
'''
conn.unbind_s()
class SearchCounter(object):
'''
Simple callback counter.
For non-paged search, callback will increase the counts.
'''
def __init__(self):
self.result_count, self.ref_count = 0, 0
def callback(self, result_type, result_data):
'''
handle a search result.
This will increment the appropriate counter.
'''
if result_type == ldap.RES_SEARCH_ENTRY:
self.result_count += 1
elif result_type == ldap.RES_SEARCH_REFERENCE:
self.ref_count += 1
def search(result_callback, *args, **kwargs):
'''
Simple non-paged search.
See ldap.search_ext for parameters.
See ldap.result for callback parameters.
'''
ldap_result_id = conn.search_ext(*args, **kwargs)
try:
while True:
result_type, result_data = conn.result(ldap_result_id, 0)
if result_data == []:
break
result_callback(result_type, result_data)
except ldap.SIZELIMIT_EXCEEDED:
conn.abandon_ext(ldap_result_id)
def paged_search(*args, **kwargs):
'''
Paged search.
This is a generator function which will yield results one at a time.
See ldap.search_ext for parameters.
See ldap.result3 for implementation details.
'''
results_control = SimplePagedResultsControl(
ldap.LDAP_CONTROL_PAGE_OID, True, (LDAPConf.page_size, ''))
while True:
serverctrls = [results_control]
msgid = conn.search_ext(*args, serverctrls=serverctrls, **kwargs)
_, results, _, serverctrls = conn.result3(msgid=msgid)
for result in results:
yield result
cookie = None
# look for the cookie to continue the search
for serverctrl in serverctrls:
if serverctrl.controlType == ldap.LDAP_CONTROL_PAGE_OID:
_, cookie = serverctrl.controlValue
if cookie:
results_control.controlValue = (LDAPConf.page_size, cookie)
break
# no cookie = search complete.
if not cookie:
break
# example
if __name__ == '__main__':
try:
connect()
counter = SearchCounter()
search(counter.callback,
LDAPConf.base_dn, LDAPConf.search_scope,
LDAPConf.search_filter, LDAPConf.retrieve_attrs)
print 'result_count', counter.result_count
count = 0
for item in paged_search(
LDAPConf.base_dn, LDAPConf.search_scope,
LDAPConf.search_filter, LDAPConf.retrieve_attrs):
count += 1
print 'paged_result_count', count
print 'last item', item
except ldap.LDAPError, e:
print e
raise
finally:
disconnect()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment