LDAP query in python

Question:

I want to execute the following query in the ldap

ldapsearch -h hostname -b dc=ernet,dc=in -x "(&(uid=w2lame)(objectClass=posixAccount))" gidnumber
ldapsearch -h hostname -b dc=ernet,dc=in -x "(&(gidNumber=1234)(objectClass=posixGroup))" cn

And use the variables thus obtained. How can I do that?

Asked By: w2lame

||

Answers:

you can use the commands module, and the getoutput to parse the result of the ldap query:

from commands import getoutput
result = getoutput('ldapsearch -h hostname -b dc=ernet,dc=in -x "(&(uid=w2lame)(objectClass=posixAccount))"')
print result

you have to have ldapsearch binary installed in your system.

Answered By: pepo

You probably want to use the ldap module. Code would look something like:

import ldap
l = ldap.initialize('ldap://ldapserver')
username = "uid=%s,ou=People,dc=mydotcom,dc=com" % username
password = "my password"
try:
    l.protocol_version = ldap.VERSION3
    l.simple_bind_s(username, password)
    valid = True
except Exception, error:
    print error
Answered By: Clarus

Here’s an example generator for python-ldap.

The ldap_server is the object you get from ldap.initialize(). You will probably need to bind before calling this function, too, depending on what LDAP server you are using and what you are trying to query for. The base_dn and filter_ are similar to what you’ve got in your command line version. The limit is the maximum number of records returned.

def _ldap_list(ldap_server, base_dn, filter_, limit=0):
    """ Generator: get a list of search results from LDAP asynchronously. """

    ldap_attributes = ["*"] # List of attributes that you want to fetch.
    result_id = ldap_server.search(base_dn, ldap.SCOPE_SUBTREE, filter_, ldap_attributes)
    records = 0

    while 1:
        records += 1

        if limit != 0 and records > limit:
            break

        try:
            result_type, result_data = ldap_server.result(result_id, 0)
        except ldap.NO_SUCH_OBJECT:
            raise DirectoryError("Distinguished name (%s) does not exist." % base_dn)

        if result_type == ldap.RES_SEARCH_ENTRY:
            dn = result_data[0][0]
            data = result_data[0][1]
            yield dn, data
        else:
            break

Please keep in mind that interpolating user-provided values into your LDAP query is dangerous! It’s a form of injection that allows a malicious user to change the meaning of the query. See: http://www.python-ldap.org/doc/html/ldap-filter.html

Answered By: Mark E. Haase

While the accepted answer does in fact show a proper way to bind to an LDAP server I do feel it didn’t answer the question holistically. Here is what I ended up implementing to grab the mail and department of a user. This somewhat blends the required attributes from the original question.

l = ldap.initialize('ldap://ldap.myserver.com:389')
binddn = "cn=myUserName,ou=GenericID,dc=my,dc=company,dc=com"
pw = "myPassword"
basedn = "ou=UserUnits,dc=my,dc=company,dc=com"
searchFilter = "(&(gidNumber=123456)(objectClass=posixAccount))"
searchAttribute = ["mail","department"]
#this will scope the entire subtree under UserUnits
searchScope = ldap.SCOPE_SUBTREE
#Bind to the server
try:
    l.protocol_version = ldap.VERSION3
    l.simple_bind_s(binddn, pw) 
except ldap.INVALID_CREDENTIALS:
  print "Your username or password is incorrect."
  sys.exit(0)
except ldap.LDAPError, e:
  if type(e.message) == dict and e.message.has_key('desc'):
      print e.message['desc']
  else: 
      print e
  sys.exit(0)
try:    
    ldap_result_id = l.search(basedn, searchScope, searchFilter, searchAttribute)
    result_set = []
    while 1:
        result_type, result_data = l.result(ldap_result_id, 0)
        if (result_data == []):
            break
        else:
            ## if you are expecting multiple results you can append them
            ## otherwise you can just wait until the initial result and break out
            if result_type == ldap.RES_SEARCH_ENTRY:
                result_set.append(result_data)
    print result_set
except ldap.LDAPError, e:
    print e
l.unbind_s()
Answered By: Dan

I cobbled this together this morning while skimming through the documentation of the ldap module. It can fulfil the requirements of the OP changing the filter and the other settings to his liking.

The documentation of the ldap module is pretty good if you understand the context (that’s what took me a while). And the module is surprinsingly easy to use. We have a similar script written in bash using ldapserach that is at least 3 or 4 times longer and more complex to read.

This code accepts a partial search string (email, name, uid or part of it) and returns the results in LDIF format. The idea is to make it very simple to use for a very specific task and if possible without using flags so that my less skilled co-workers can find the relevant info quickly.

Note that this is written for an LDAP server that runs on a machine that is not accessible from outside our internal network and which is secured with 2FA authentication. It can, thus, safely accept anonymous queries. But adding user and password should be trivial.

   #! /usr/bin/python3
    
    ### usearch
    ### searches in the LDAP database for part of a name, uid or email and returns mail, uid, and full name
    
    import ldap
    import argparse
    import sys
    import ldif
    
    l = ldap.initialize('ldaps://your.fancy.server.url', bytes_mode=False)
    
    basedn = "dc=foo,dc=bar,dc=baz"
    
    
    ## ARGPARSE stuff!!! 
    
    parser=argparse.ArgumentParser(
        description ='searches the LDAP server', 
        usage='usearch PARTIAL_MATCH (email, name, username)',
        formatter_class = argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument('istr', help='searches stuffz')
    parser.print_help
    args = parser.parse_args(None if sys.argv[1:] else ['-h'])
   
   str1 = args.istr
    
    
    sfilter = "(|(sn=*{}*)(mail=*{}*)(uid=*{}*))".format(str1,str1,str1)
    attributes = ["mail","uid","cn"]
    scope = ldap.SCOPE_SUBTREE
    
    r = l.search_s(basedn,scope,sfilter,attributes)
    
    ldif_writer=ldif.LDIFWriter(sys.stdout)
    
    for dn, entry in r:
        ldif_writer.unparse(dn,entry)

And as I was at it, here a version with the ldap3 module. The argparse part is copy-pasted. This time the output is "human readable", instead of LDIF:

#! /usr/bin/python3
## usearch3
## LDAP3 version 

import ldap3
import argparse
import sys

server = ldap3.Server('ldaps://foo.bar.baz')
conn = ldap3.Connection(server)
conn.bind()
basedn = 'dc=foobar,dc=dorq,dc=baz'
attribs = ['mail','uid','cn']

parser=argparse.ArgumentParser(
    description ='searches the LDAP server and returns user, full name and email. Accepts any partial entry', 
    usage='usearch3 PARTIAL_MATCH (email, name, username)',
    formatter_class = argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('istr', help='searches stuffz')
parser.print_help
args = parser.parse_args(None if sys.argv[1:] else ['-h'])

str1 = args.istr

sfilter = "(|(sn=*{}*)(mail=*{}*)(uid=*{}*))".format(str1,str1,str1)

conn.search(basedn,sfilter)
conn.search(basedn,sfilter,attributes = attribs)

leng = len(conn.entries)
for i in range(leng):
   
    user = conn.entries[i].uid
    fullname = conn.entries[i].cn
    email = conn.entries[i].mail
    
    print("user:t{}nname:t{}nemail:t{}nn".format(user,fullname,email)) 
    
Answered By: runlevel0
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.