2

Background: I am working on an API to centralize user creation and management for multiple resources(e.g. Google Apps, Dropbox, etc...). On a Linux VM, I developed an API and web interface that allows me(and my co-admins) to authenticate and manage user accounts for these services. The next thing I need to integrate is our Active Directory which is hosted on a remote Windows Server 2008.

I have been trying to use python-ldap to connect to and retrieve/modify information but have had issues with DIR_ERROR operations errors(when trying to query for users) and NAMING_VIOLATION errors(when trying to add users).

*Code based on http://www.grotan.com/ldap/python-ldap-samples.html, stackoverflow questions, and python-ldap documentation Binding code that I believe works:

import ldap
try:
    l = ldap.open("serverip")
    l.protocol_version = ldap.VERSION3  

    username = "[email protected]"
    password  = "secret"

    result = l.simple_bind(username, password)
    print result

except ldap.LDAPError, e:
    print e

which prints: (97, [], 1, [])

Query for users script: (tried without bind as suggested by article, but received "In order to perform this operation a successful bind must be completed on the connection.")

import ldap

try:
    l = ldap.open("serverIp", port=389)
    l.protocol_version = ldap.VERSION3  
    username = "[email protected]"
    password  = "secret"

    result = l.simple_bind(username, password)
    print result
except ldap.LDAPError, e:
    print e
    # handle error however you like

baseDN = "ou=Users, o=adtest.local"
searchScope = ldap.SCOPE_SUBTREE
retrieveAttributes = None 
searchFilter = "cn=*myUserName*"

try:
    ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
    result_set = []
    while 1:
        result_type, result_data = l.result(ldap_result_id, 0)
        if (result_data == []):
            break
        else:
            if result_type == ldap.RES_SEARCH_ENTRY:
                result_set.append(result_data)
    print result_set
except ldap.LDAPError, e:
    print e

which results in the following: (97, [], 1, []) {'info': '000020D6: SvcErr: DSID-031007DB, problem 5012 (DIR_ERROR), data 0\n','desc':'Operations error'}

Add user script:(using ldaps)

import ldap
import ldap.modlist as modlist

# Open a connection
l = ldap.initialize("ldaps://serverIp:636/")

# Bind/authenticate with a user with apropriate rights to add objects
l.simple_bind_s("[email protected]","secret")

# The dn of our new entry/object
dn="cn=test,dc=adtest,dc=local" 

# A dict to help build the "body" of the object
attrs = {}
attrs['objectclass'] = ['top','organizationalRole','simpleSecurityObject']
attrs['cn'] = 'test'
attrs['userPassword'] = 'aDifferentSecret'
attrs['description'] = 'test user'

# Convert our dict to nice syntax for the add-function using modlist-module
ldif = modlist.addModlist(attrs)

# Do the add-operation to the ldapserver
l.add_s(dn,ldif)

# Disconnect and free resources when done
l.unbind_s()

Which results in: ldap.SERVER_DOWN: {'info': 'A TLS packet with unexpected length was received.','desc': "Can't contact LDAP server"}

*This made me think that the port might be the problem, so I changed the initialize line to l = ldap.initialize("ldap://serverIp:389/") similar to the other two scripts.

Now I get: ldap.NAMING_VIOLATION: {'info': "00002099: NameErr: DSID-0305109C, problem 2005 (NAMING_VIOLATION), data 0, best match of:\n\t'dc=adtest, dc=local'\n", 'desc': 'Naming violation'}

Additionally, I have messed around with adding the ou and uid to the attrs but no change in error.

What am I doing wrong or what could I try to do differently? Thank you for any help/suggestions!

edit: I checked my server, and port 636 is correctly set to allow Secure LDAP traffic, so I don't know why that was giving me different errors than the normal LDAP. edit2: I tried changing the following line in my add script dn="cn=test,dc=adtest.local"

and the new output(stack trace) I have is(I added the print statement in to show that the bind is actually taking place now before the error):
(97, [], 1, [])
Traceback (most recent call last):
File "test2.py", line 21, in <module>
l.add_s(dn,ldif)
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 202, in add_s
return self.result(msgid,all=1,timeout=self.timeout)
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 465, in result
resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 469, in result2
resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 476, in result3
resp_ctrl_classes=resp_ctrl_classes
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 483, in result4
ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
File "/usr/local/lib/python2.7/dist-packages/ldap/ldapobject.py", line 106, in _ldap_call
result = func(*args,**kwargs)
ldap.REFERRAL: {'info': 'Referral:\nldap://adtest.local/cn=test,dc=adtest.local', 'desc': 'Referral'}

1 Answer 1

6

Working Query search!
Credit to:
http://www.linuxjournal.com/article/6988?page=0,0

import ldap

def main():

    keyword = "user_query"

    try:
        l = ldap.open(serverIp)
        l.simple_bind_s("[email protected]", "password")
        print "successfully bound to server.\n"

        print "Searching..\n"
        my_search(l,keyword)
    except ldap.LDAPError, e:
        print "Couldn't connect. %s " % e

def my_search(l, keyword):
    #Base is for the DN(Distinguised Name) of the entry where the search should start
    base = "cn=Users,dc=adtest,dc=local"
    #Scope has three options, SUBTREE searches all sub-folder/directories
    scope = ldap.SCOPE_SUBTREE
    #filter consists of a cn(common name) and keyword.
    #putting asterisks around our keyword will match anything containing the string
    f = "cn=" + "*" + keyword + "*"
    #determines which attributes to return. Returns all if set to "None"
    retrieve_attributes = None

    count = 0
    result_set = []
    timeout = 0
    result = l.search_s(base, scope, f, retrieve_attributes)
    print result[0][1].keys()
    try:
        result_id = l.search(base, scope, f, retrieve_attributes)
        while 1:
            result_type, result_data = l.result(result_id, timeout)
            if(result_data == []):
                break
            else:
                if result_type == ldap.RES_SEARCH_ENTRY:
                    result_set.append(result_data)
        if len(result_set) == 0:
            print "No Results"
            return
        for i in range(len(result_set)):
            for entry in result_set[i]:
                try:
                    name = entry[1]['cn'][0]
                    count += 1
                    print str(count)+" "+name
                except:
                    pass
    except ldap.LDAPError, e:
        print e

if __name__=='__main__':
    main()

I fixed the one mistake in my code, but still couldn't set certain properties because LDAP uses plain-text and does not allow private info to be sent without a secure connection. In order to add/modify user password info and userAccountControl flags(to enable a user), I switched to LDAPS using port 636, which I enabled on the server by adding Active Directory Certificate Services(*requires you to restart the server).
Additionally you need to include the 'ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,0)' line before you initialize.

Working Add User
Credit to:
how to set lockoutTime and password of a user of Active Directory

import ldap
import ldap.modlist as modlist

ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,0)
l = ldap.initialize("ldaps://10.99.0.214:636")
l.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
l.set_option(ldap.OPT_NETWORK_TIMEOUT, 10.0)
t = l.simple_bind_s("[email protected]","password")

dn="cn=TestUser,cn=Users,dc=adtest,dc=local"

#make a unicode password to set for user
unicode_pass = unicode('\"'+"userPwd"+'\"', 'iso-8859-1')
password_value = unicode_pass.encode('utf-16-le')

#What I set for my users, you can find more by looking through a user's properties on your DC.
attrs = {}
attrs['cn'] = 'TestUser'
attrs['displayName'] = 'TestUser'
attrs['givenName'] = 'Test'
attrs['mail'] = '[email protected]'
attrs['name'] = 'Test User'
attrs['objectclass'] = ['top','person','organizationalPerson','user']
attrs['sAMAccountName'] = 'testuser'
attrs['sn'] = 'User'
attrs['unicodepwd'] = password_value
attrs['userPrincipalName'] = '[email protected]'

ldif = modlist.addModlist(attrs)

l.add_s(dn,ldif)

#Now that the user is created and has a password(needs to meet AD requirements), they can be enabled 

#For full userAccountControl flag list:
#http://support.microsoft.com/en-us/kb/305144
mod_acct = [(ldap.MOD_REPLACE, 'userAccountControl', '66048')]
try:
    l.modify_s(dn, mod_acct)
except ldap.LDAPError, e:
    print e

l.unbind_s()
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.