diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..072e2034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ + +# Auto-generated +.*.swp +*.pyc +__pycache__/ +.tox + +# shared libs installed by 'setup.py test' +/Lib/*.so* +/Lib/*.dylib +/Lib/*.pyd + +# Build related +*.egg-info +build/ +dist/ +PKG-INFO diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..542377d8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: python + +python: +- '2.7' +- '3.3' +- '3.4' +- '3.5' +- '3.6' +# Note: when updating Python versions, also change setup.py and tox.ini + +sudo: false + +cache: pip + +addons: + apt: + packages: + - ldap-utils + - slapd + +install: + - pip install "pip>=7.1.0" + - pip install tox-travis tox + +script: tox diff --git a/CHANGES b/CHANGES index 4d879e42..113eb275 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,76 @@ +---------------------------------------------------------------- +Released 3.0.0 xxxx-xx-xx + +Changes since 2.4.45: + +Mandatory prerequisites: +- Python 2.7.x or 3.3+ +- pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ + +Python 3 support is merged from the pyldap fork (https://github.com/pyldap) + +Infrastructure: +- Add .gitignore +- Re-format README to ReStructured Text +- Setup for automatic testing using Travis CI + +Modules/ +(thanks to Michael Ströder) +* removed unused code schema.c +* moved code from version.c to ldapmodule.c +* removed obsolete back-ward compability constants from common.h +* build checks whether LDAP_API_VERSION is OpenLDAP 2.4.x +* _ldap.__author__ and _ldap.__license__ also set from ldap.pkginfo +* assume C extension API for Python 2.7+ + +Lib/ +(thanks to Michael Ströder) +* ldap.__version__, ldap.__author__ and ldap.__license__ now + imported from new sub-module ldap.pkginfo also to setup.py +* Added safety assertion when importing _ldap: + ldap.pkginfo.__version__ must match _ldap.__version__ +* removed stand-alone module dsml +* slapdtest.SlapdObject.restart() just restarts slapd + without cleaning any data +* Compability changes for pyasn1 0.3.x or newer + (thanks to Ilya Etingof and Christian Heimes) +* The methods SSSResponseControl.decodeControlValue() and + VLVResponseControl.decodeControlValue() now follow the coding + convention to use camel-cased ASN.1 name as class attribute name. + The old class names are still set for back-ward compability + but should not be used in new code because they might be removed + in a later release. +* removed SSSRequestControl from ldap.controls.KNOWN_RESPONSE_CONTROLS +* removed all dependencies on modules string and types +* removed use of .has_key() +* removed class ldap.ldapobject.NonblockingLDAPObject +* new global constant ldap.LIBLDAP_API_INFO +* right after importing _ldap there is a call into libldap to initialize it +* method .decodeControlValue() of SSSResponseControl and VLVResponseControl + does not set class attribute result_code anymore +* always use bytes() for UUID() constructor in ldap.syncrepl +* module ldif now uses functions b64encode() and b64decode() +* fixed pickling and restoring of ReconnectLDAPObject + +Lib/slapdtest.py +* Automatically try some common locations for SCHEMADIR +* Ensure server is stopped when the process exits + +Tests/ +(thanks to Michael Ströder) +* added explicit reconnect tests for ReconnectLDAPObject +* scripts do not directly call SlapdTestCase.setUpClass() anymore +* added LDIF test with folded, base64-encoded attribute +* added more tests for sub-module ldap.dn +* added tests for ldap.syncrepl (thanks to Karl Kornel) + +Tests/ +(thanks to pyldap contributors): +* Expand cidict membership test +* Add test suite for binds +* Add test suite for edits +* Add a smoke-check for listall() and attribute_types() + ---------------------------------------------------------------- Released 2.4.45 2017-10-09 diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 58df3b36..68d3643e 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import ldap,ldap.async class DeleteLeafs(ldap.async.AsyncSearchHandler): @@ -15,7 +17,7 @@ def __init__(self,l): def startSearch(self,searchRoot,searchScope): if not searchScope in [ldap.SCOPE_ONELEVEL,ldap.SCOPE_SUBTREE]: - raise ValueError, "Parameter searchScope must be either ldap.SCOPE_ONELEVEL or ldap.SCOPE_SUBTREE." + raise ValueError("Parameter searchScope must be either ldap.SCOPE_ONELEVEL or ldap.SCOPE_SUBTREE.") self.nonLeafEntries = [] self.deletedEntries = 0 ldap.async.AsyncSearchHandler.startSearch( @@ -28,7 +30,7 @@ def startSearch(self,searchRoot,searchScope): ) def _processSingleResult(self,resultType,resultItem): - if self._entryResultTypes.has_key(resultType): + if resultType in self._entryResultTypes: # Don't process search references dn,entry = resultItem hasSubordinates = entry.get( @@ -45,7 +47,7 @@ def _processSingleResult(self,resultType,resultItem): else: try: self._l.delete_s(dn) - except ldap.NOT_ALLOWED_ON_NONLEAF,e: + except ldap.NOT_ALLOWED_ON_NONLEAF as e: self.nonLeafEntries.append(dn) else: self.deletedEntries = self.deletedEntries+1 @@ -62,7 +64,7 @@ def DelTree(l,dn,scope=ldap.SCOPE_ONELEVEL): non_leaf_entries = leafs_deleter.nonLeafEntries[:] while non_leaf_entries: dn = non_leaf_entries.pop() - print deleted_entries,len(non_leaf_entries),dn + print(deleted_entries,len(non_leaf_entries),dn) leafs_deleter.startSearch(dn,ldap.SCOPE_SUBTREE) leafs_deleter.processResults() deleted_entries = deleted_entries+leafs_deleter.deletedEntries diff --git a/Demo/Lib/ldap/async/ldifwriter.py b/Demo/Lib/ldap/async/ldifwriter.py index 8cc4aa53..96717625 100644 --- a/Demo/Lib/ldap/async/ldifwriter.py +++ b/Demo/Lib/ldap/async/ldifwriter.py @@ -7,9 +7,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldap,ldap.async diff --git a/Demo/Lib/ldap/async/sizelimit.py b/Demo/Lib/ldap/async/sizelimit.py index 05e439b1..11ed7b68 100644 --- a/Demo/Lib/ldap/async/sizelimit.py +++ b/Demo/Lib/ldap/async/sizelimit.py @@ -8,9 +8,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldap,ldap.async diff --git a/Demo/Lib/ldapurl/urlsearch.py b/Demo/Lib/ldapurl/urlsearch.py index 996e6dac..b293aa20 100644 --- a/Demo/Lib/ldapurl/urlsearch.py +++ b/Demo/Lib/ldapurl/urlsearch.py @@ -3,30 +3,30 @@ No output of LDAP data is produced except trace output. """ - +from __future__ import print_function import sys,getpass,ldap,ldapurl try: ldapUrl = ldapurl.LDAPUrl(ldapUrl=sys.argv[1]) except IndexError: - print 'Usage: %s [LDAP URL]' % (sys.argv[0]) + print('Usage: %s [LDAP URL]' % (sys.argv[0])) sys.exit(1) for a in [ 'urlscheme','hostport','dn','attrs','scope', 'filterstr','extensions','who','cred' ]: - print a,repr(getattr(ldapUrl,a)) + print(a,repr(getattr(ldapUrl,a))) l = ldap.initialize(ldapUrl.initializeUrl(),trace_level=1) if ldapUrl.who!=None: if ldapUrl.cred!=None: cred=ldapUrl.cred else: - print 'Enter password for simple bind with',repr(ldapUrl.who) + print('Enter password for simple bind with',repr(ldapUrl.who)) cred=getpass.getpass() l.simple_bind_s(ldapUrl.who,cred) res = l.search_s(ldapUrl.dn,ldapUrl.scope,ldapUrl.filterstr,ldapUrl.attrs) -print len(res),'search results' +print(len(res),'search results') diff --git a/Demo/Lib/ldif/ldifcopy.py b/Demo/Lib/ldif/ldifcopy.py index 498b857a..62cb3919 100644 --- a/Demo/Lib/ldif/ldifcopy.py +++ b/Demo/Lib/ldif/ldifcopy.py @@ -7,9 +7,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldif diff --git a/Demo/initialize.py b/Demo/initialize.py index ab2647e7..952b3f4b 100644 --- a/Demo/initialize.py +++ b/Demo/initialize.py @@ -7,6 +7,7 @@ ldaps://localhost:1391 (LDAP over SSL) ldapi://%2ftmp%2fopenldap2 (domain socket /tmp/openldap2) """ +from __future__ import print_function import sys,os,ldap @@ -23,10 +24,10 @@ # Complete path name of the file containing all trusted CA certs CACERTFILE='/etc/ssl/ca-bundle.pem' -print """################################################################## +print("""################################################################## # LDAPv3 connection with StartTLS ext. op. ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldap://localhost:1390',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) @@ -44,8 +45,8 @@ # Now try StartTLS extended operation l.start_tls_s() -print '***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION) -print '***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER) +print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION)) +print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER)) # Try an explicit anon bind to provoke failure l.simple_bind_s('','') @@ -53,10 +54,10 @@ # Close connection l.unbind_s() -print """################################################################## +print("""################################################################## # LDAPv3 connection over SSL ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldaps://localhost:1391',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) @@ -74,16 +75,16 @@ # Try an explicit anon bind to provoke failure l.simple_bind_s('','') -print '***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION) -print '***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER) +print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION)) +print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER)) # Close connection l.unbind_s() -print """################################################################## +print("""################################################################## # LDAPv3 connection over Unix domain socket ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldapi://%2ftmp%2fopenldap-socket',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) diff --git a/Demo/ldapcontrols.py b/Demo/ldapcontrols.py index 214042d3..a5ba8d34 100644 --- a/Demo/ldapcontrols.py +++ b/Demo/ldapcontrols.py @@ -1,15 +1,16 @@ +from __future__ import print_function import ldap,ldapurl,pprint from ldap.controls import LDAPControl,BooleanControl l = ldap.initialize('ldap://localhost:1390',trace_level=2) -print 60*'#' +print(60*'#') pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS)) l.manage_dsa_it(1,1) pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS)) -print 60*'#' +print(60*'#') # Search with ManageDsaIT control (which has no value) pprint.pprint(l.search_ext_s( @@ -19,7 +20,7 @@ ['*','+'], serverctrls = [ LDAPControl('2.16.840.1.113730.3.4.2',1,None) ], )) -print 60*'#' +print(60*'#') # Search with Subentries control (which has boolean value) pprint.pprint(l.search_ext_s( @@ -30,4 +31,4 @@ serverctrls = [ BooleanControl('1.3.6.1.4.1.4203.1.10.1',1,1) ], )) -print 60*'#' +print(60*'#') diff --git a/Demo/ldapurl_search.py b/Demo/ldapurl_search.py index 2c630090..07ffbca5 100644 --- a/Demo/ldapurl_search.py +++ b/Demo/ldapurl_search.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import sys,pprint,ldap from ldap.ldapobject import LDAPObject @@ -15,7 +17,7 @@ class MyLDAPUrl(LDAPUrl): ldap_url = MyLDAPUrl(sys.argv[1]) trace_level = int(ldap_url.trace_level or '0') -print '***trace_level',trace_level +print('***trace_level',trace_level) ldap.trace_level = trace_level @@ -37,6 +39,6 @@ class MyLDAPUrl(LDAPUrl): pprint.pprint(result) -print '***DIAGNOSTIC_MESSAGE',repr(l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE)) +print('***DIAGNOSTIC_MESSAGE',repr(l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE))) l.unbind_s() diff --git a/Demo/matchedvalues.py b/Demo/matchedvalues.py index 4de3e8a4..59c594ff 100644 --- a/Demo/matchedvalues.py +++ b/Demo/matchedvalues.py @@ -27,16 +27,17 @@ # Matched values control: (mail=*@example.org) # dn: uid=jsmith,ou=People,dc=example,dc=com # mail: jsmith@example.org +from __future__ import print_function import ldap from ldap.controls import MatchedValuesControl def print_result(search_result): for n in range(len(search_result)): - print "dn: %s" % search_result[n][0] + print("dn: %s" % search_result[n][0]) for attr in search_result[n][1].keys(): for i in range(len(search_result[n][1][attr])): - print "%s: %s" % (attr, search_result[n][1][attr][i]) + print("%s: %s" % (attr, search_result[n][1][attr][i])) print @@ -51,13 +52,13 @@ def print_result(search_result): mv = MatchedValuesControl(criticality=True, controlValue=control_filter) res = ld.search_ext_s(base, scope, filter, attrlist = ['mail']) -print "LDAP filter used: %s" % filter -print "Requesting 'mail' attribute back" +print("LDAP filter used: %s" % filter) +print("Requesting 'mail' attribute back") print -print "No matched values control:" +print("No matched values control:") print_result(res) res = ld.search_ext_s(base, scope, filter, attrlist = ['mail'], serverctrls = [mv]) -print "Matched values control: %s" % control_filter +print("Matched values control: %s" % control_filter) print_result(res) diff --git a/Demo/options.py b/Demo/options.py index fb372093..8b4e2159 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -1,27 +1,28 @@ +from __future__ import print_function import ldap host="localhost:1390" -print "API info:",ldap.get_option(ldap.OPT_API_INFO) -print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL) -#print "Setting debug level to 255..." +print("API info:",ldap.get_option(ldap.OPT_API_INFO)) +print("debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)) +#print("Setting debug level to 255...") #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) -#print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL) -print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT) -print "Setting default size limit to 10..." +#print("debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)) +print("default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)) +print("Setting default size limit to 10...") ldap.set_option(ldap.OPT_SIZELIMIT,10) -print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT) -print "Creating connection to",host,"..." +print("default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)) +print("Creating connection to",host,"...") l=ldap.init(host) -print "size limit:",l.get_option(ldap.OPT_SIZELIMIT) -print "Setting connection size limit to 20..." +print("size limit:",l.get_option(ldap.OPT_SIZELIMIT)) +print("Setting connection size limit to 20...") l.set_option(ldap.OPT_SIZELIMIT,20) -print "size limit:",l.get_option(ldap.OPT_SIZELIMIT) -#print "Setting time limit to 60 secs..." +print("size limit:",l.get_option(ldap.OPT_SIZELIMIT)) +#print("Setting time limit to 60 secs...") l.set_option(ldap.OPT_TIMELIMIT,60) -#print "time limit:",l.get_option(ldap.OPT_TIMELIMIT) -print "Binding..." +#print("time limit:",l.get_option(ldap.OPT_TIMELIMIT)) +print("Binding...") l.simple_bind_s("","") diff --git a/Demo/page_control.py b/Demo/page_control.py index 0fc904b4..8238ede3 100644 --- a/Demo/page_control.py +++ b/Demo/page_control.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import print_function url = "ldap://localhost:1390" base = "dc=stroeder,dc=de" @@ -41,20 +42,20 @@ pages = 0 while True: pages += 1 - print '-'*60 - print "Getting page %d" % (pages) + print('-'*60) + print("Getting page %d" % (pages)) rtype, rdata, rmsgid, serverctrls = l.result3(msgid,resp_ctrl_classes=known_ldap_resp_ctrls) - print '%d results' % len(rdata) - print 'serverctrls=',pprint.pprint(serverctrls) - print 'rdata=',pprint.pprint(rdata) + print('%d results' % len(rdata)) + print('serverctrls=',pprint.pprint(serverctrls)) + print('rdata=',pprint.pprint(rdata)) pctrls = [ c for c in serverctrls if c.controlType == SimplePagedResultsControl.controlType ] if pctrls: - print 'pctrls[0].size',repr(pctrls[0].size) - print 'pctrls[0].cookie',repr(pctrls[0].cookie) + print('pctrls[0].size',repr(pctrls[0].size)) + print('pctrls[0].cookie',repr(pctrls[0].cookie)) if pctrls[0].cookie: # Copy cookie from response control to request control req_ctrl.cookie = pctrls[0].cookie @@ -68,7 +69,7 @@ else: break else: - print "Warning: Server ignores RFC 2696 control." + print("Warning: Server ignores RFC 2696 control.") break l.unbind_s() diff --git a/Demo/paged_search_ext_s.py b/Demo/paged_search_ext_s.py index d0e0982f..d0f82918 100644 --- a/Demo/paged_search_ext_s.py +++ b/Demo/paged_search_ext_s.py @@ -1,3 +1,4 @@ +from __future__ import print_function url = "ldap://localhost:1390/" base = "dc=stroeder,dc=de" search_flt = r'(objectClass=*)' @@ -72,7 +73,7 @@ def paged_search_ext_s(self,base,scope,filterstr='(objectClass=*)',attrlist=None else: break # no more pages available - except ldap.SERVER_DOWN,e: + except ldap.SERVER_DOWN as e: try: self.reconnect(self._uri) except AttributeError: @@ -104,4 +105,4 @@ class MyLDAPObject(ReconnectLDAPObject,PagedResultsSearchObject): l.unbind_s() -print 'Received %d results in %d pages.' % (len(all_results),result_pages) +print('Received %d results in %d pages.' % (len(all_results),result_pages)) diff --git a/Demo/passwd_ext_op.py b/Demo/passwd_ext_op.py index 1030f0eb..cc5d22cd 100644 --- a/Demo/passwd_ext_op.py +++ b/Demo/passwd_ext_op.py @@ -1,6 +1,7 @@ """ Example showing the use of the password extended operation. """ +from __future__ import print_function import sys,ldap,ldapurl,getpass @@ -11,9 +12,9 @@ lu = ldapurl.LDAPUrl(sys.argv[1]) -print 'Old password' +print('Old password') oldpw = getpass.getpass() -print 'New password' +print('New password') newpw = getpass.getpass() # Set path name of file containing all CA certificates diff --git a/Demo/pyasn1/dds.py b/Demo/pyasn1/dds.py index 656b4397..c803a1de 100644 --- a/Demo/pyasn1/dds.py +++ b/Demo/pyasn1/dds.py @@ -8,6 +8,7 @@ pyasn1-modules python-ldap 2.4+ """ +from __future__ import print_function from ldap.extop.dds import RefreshRequest,RefreshResponse @@ -16,8 +17,8 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) request_ttl = int(sys.argv[2]) -except IndexError,ValueError: - print 'Usage: dds.py ' +except (IndexError, ValueError): + print('Usage: dds.py ') sys.exit(1) # Set debugging level @@ -32,24 +33,24 @@ ) if ldap_url.cred is None: - print 'Password for %s:' % (repr(ldap_url.who)) + print('Password for %s:' % (repr(ldap_url.who))) ldap_url.cred = getpass.getpass() try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) +except ldap.INVALID_CREDENTIALS as e: + print('Simple bind failed:',str(e)) sys.exit(1) else: extreq = RefreshRequest(entryName=ldap_url.dn,requestTtl=request_ttl) try: extop_resp_obj = ldap_conn.extop_s(extreq,extop_resp_class=RefreshResponse) - except ldap.LDAPError,e: - print str(e) + except ldap.LDAPError as e: + print(str(e)) else: if extop_resp_obj.responseTtl!=request_ttl: - print 'Different response TTL:',extop_resp_obj.responseTtl + print('Different response TTL:',extop_resp_obj.responseTtl) else: - print 'Response TTL:',extop_resp_obj.responseTtl + print('Response TTL:',extop_resp_obj.responseTtl) diff --git a/Demo/pyasn1/derefcontrol.py b/Demo/pyasn1/derefcontrol.py index 10985293..0e7153de 100644 --- a/Demo/pyasn1/derefcontrol.py +++ b/Demo/pyasn1/derefcontrol.py @@ -3,6 +3,7 @@ This sample script demonstrates the use of the dereference control (see https://tools.ietf.org/html/draft-masarati-ldap-deref) """ +from __future__ import print_function import pprint,ldap,ldap.modlist,ldap.resiter @@ -29,8 +30,8 @@ class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor): } ) -print 'pyasn1 output of request control:' -print dc._derefSpecs().prettyPrint() +print('pyasn1 output of request control:') +print(dc._derefSpecs().prettyPrint()) msg_id = l.search_ext( 'dc=demo1,dc=freeipa,dc=org', @@ -43,6 +44,6 @@ class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor): for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id,add_ctrls=1): for dn,entry,deref_control in res_data: # process dn and entry - print dn,entry['objectClass'] + print(dn,entry['objectClass']) if deref_control: pprint.pprint(deref_control[0].derefRes) diff --git a/Demo/pyasn1/noopsearch.py b/Demo/pyasn1/noopsearch.py index 08635dea..2045f50c 100644 --- a/Demo/pyasn1/noopsearch.py +++ b/Demo/pyasn1/noopsearch.py @@ -9,6 +9,7 @@ pyasn1-modules python-ldap 2.4+ """ +from __future__ import print_function import sys,ldap,ldapurl,getpass @@ -19,7 +20,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) except IndexError: - print 'Usage: noopsearch.py ' + print('Usage: noopsearch.py ') sys.exit(1) # Set debugging level @@ -34,14 +35,14 @@ ) if ldap_url.who and ldap_url.cred is None: - print 'Password for %s:' % (repr(ldap_url.who)) + print('Password for %s:' % (repr(ldap_url.who))) ldap_url.cred = getpass.getpass() try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) +except ldap.INVALID_CREDENTIALS as e: + print('Simple bind failed:',str(e)) sys.exit(1) try: @@ -58,7 +59,7 @@ ldap.TIMEOUT, ldap.TIMELIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED, - ldap.ADMINLIMIT_EXCEEDED),e: + ldap.ADMINLIMIT_EXCEEDED) as e: ldap_conn.abandon(msg_id) sys.exit(1) @@ -69,5 +70,5 @@ if c.controlType==SearchNoOpControl.controlType ][0] -print 'Number of search results: %d' % noop_srch_ctrl.numSearchResults -print 'Number of search continuations: %d' % noop_srch_ctrl.numSearchContinuations +print('Number of search results: %d' % noop_srch_ctrl.numSearchResults) +print('Number of search continuations: %d' % noop_srch_ctrl.numSearchContinuations) diff --git a/Demo/pyasn1/ppolicy.py b/Demo/pyasn1/ppolicy.py index 8722356c..cf6b2ac9 100644 --- a/Demo/pyasn1/ppolicy.py +++ b/Demo/pyasn1/ppolicy.py @@ -9,6 +9,7 @@ pyasn1-modules python-ldap 2.4+ """ +from __future__ import print_function import sys,ldap,ldapurl,getpass @@ -16,8 +17,8 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) -except IndexError,ValueError: - print 'Usage: ppolicy.py ' +except (IndexError,ValueError): + print('Usage: ppolicy.py ') sys.exit(1) # Set debugging level @@ -32,19 +33,19 @@ ) if ldap_url.cred is None: - print 'Password for %s:' % (repr(ldap_url.who)) + print('Password for %s:' % (repr(ldap_url.who))) ldap_url.cred = getpass.getpass() try: msgid = ldap_conn.simple_bind(ldap_url.who,ldap_url.cred,serverctrls=[PasswordPolicyControl()]) res_type,res_data,res_msgid,res_ctrls = ldap_conn.result3(msgid) -except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) +except ldap.INVALID_CREDENTIALS as e: + print('Simple bind failed:',str(e)) sys.exit(1) else: if res_ctrls[0].controlType==PasswordPolicyControl.controlType: ppolicy_ctrl = res_ctrls[0] - print 'PasswordPolicyControl' - print 'error',repr(ppolicy_ctrl.error),(ppolicy_ctrl.error!=None)*repr(PasswordPolicyError(ppolicy_ctrl.error)) - print 'timeBeforeExpiration',repr(ppolicy_ctrl.timeBeforeExpiration) - print 'graceAuthNsRemaining',repr(ppolicy_ctrl.graceAuthNsRemaining) + print('PasswordPolicyControl') + print('error',repr(ppolicy_ctrl.error),(ppolicy_ctrl.error!=None)*repr(PasswordPolicyError(ppolicy_ctrl.error))) + print('timeBeforeExpiration',repr(ppolicy_ctrl.timeBeforeExpiration)) + print('graceAuthNsRemaining',repr(ppolicy_ctrl.graceAuthNsRemaining)) diff --git a/Demo/pyasn1/psearch.py b/Demo/pyasn1/psearch.py index 02852b19..3bd59e6d 100644 --- a/Demo/pyasn1/psearch.py +++ b/Demo/pyasn1/psearch.py @@ -10,6 +10,7 @@ pyasn1-modules python-ldap 2.4+ """ +from __future__ import print_function import sys,ldap,ldapurl,getpass @@ -18,7 +19,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) except IndexError: - print 'Usage: psearch.py ' + print('Usage: psearch.py ') sys.exit(1) # Set debugging level @@ -33,14 +34,14 @@ ) if ldap_url.cred is None: - print 'Password for %s:' % (repr(ldap_url.who)) + print('Password for %s:' % (repr(ldap_url.who))) ldap_url.cred = getpass.getpass() try: ldap_conn.simple_bind_s(ldap_url.who,ldap_url.cred) -except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) +except ldap.INVALID_CREDENTIALS as e: + print('Simple bind failed:',str(e)) sys.exit(1) psc = PersistentSearchControl() @@ -64,7 +65,7 @@ resp_ctrl_classes={EntryChangeNotificationControl.controlType:EntryChangeNotificationControl}, ) except ldap.TIMEOUT: - print 'Timeout waiting for results...' + print('Timeout waiting for results...') else: for dn,entry,srv_ctrls in res_data: ecn_ctrls = [ @@ -76,4 +77,4 @@ if ecn_ctrls: changeType,previousDN,changeNumber = ecn_ctrls[0].changeType,ecn_ctrls[0].previousDN,ecn_ctrls[0].changeNumber change_type_desc = CHANGE_TYPES_STR[changeType] - print 'changeType: %s (%d), changeNumber: %s, previousDN: %s' % (change_type_desc,changeType,changeNumber,repr(previousDN)) + print('changeType: %s (%d), changeNumber: %s, previousDN: %s' % (change_type_desc,changeType,changeNumber,repr(previousDN))) diff --git a/Demo/pyasn1/readentrycontrol.py b/Demo/pyasn1/readentrycontrol.py index b52ea910..10faa2b2 100644 --- a/Demo/pyasn1/readentrycontrol.py +++ b/Demo/pyasn1/readentrycontrol.py @@ -4,6 +4,7 @@ Originally contributed by Andreas Hasenack """ +from __future__ import print_function import pprint,ldap,ldap.modlist @@ -14,10 +15,10 @@ l = ldap.initialize(uri,trace_level=2) l.simple_bind_s('uid=diradm,ou=schulung,dc=stroeder,dc=local','testsecret') -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Add new entry #--------------------------------------------------------------------------- -""" +""") new_test_dn = "uid=ablume,ou=Users,ou=schulung,dc=stroeder,dc=local" new_test_dn2 = "uid=ablume2,ou=Users,ou=schulung,dc=stroeder,dc=local" @@ -38,13 +39,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Modify entry #--------------------------------------------------------------------------- -""" +""") pr = PreReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN']) @@ -54,8 +55,8 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) pr = PostReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN']) @@ -65,13 +66,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Rename entry #--------------------------------------------------------------------------- -""" +""") pr = PostReadControl(criticality=True,attrList=['uid']) msg_id = l.rename( @@ -81,8 +82,8 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) pr = PreReadControl(criticality=True,attrList=['uid']) msg_id = l.rename( @@ -92,13 +93,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Delete entry #--------------------------------------------------------------------------- -""" +""") pr = PreReadControl(criticality=True,attrList=['*','+']) msg_id = l.delete_ext( @@ -106,5 +107,5 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 1a1eab10..91909a3a 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -8,6 +8,8 @@ https://tools.ietf.org/html/draft-wahl-ldap-session-03 """ +from __future__ import print_function + __version__ = '0.1' import sys,getpass,ldap,ldapurl @@ -16,8 +18,8 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) -except IndexError,ValueError: - print 'Usage: %s ' % (sys.argv[0]) +except (IndexError, ValueError): + print('Usage: %s ' % (sys.argv[0])) sys.exit(1) # Set debugging level @@ -32,14 +34,14 @@ ) if ldap_url.who and ldap_url.cred is None: - print 'Password for %s:' % (repr(ldap_url.who)) + print('Password for %s:' % (repr(ldap_url.who))) ldap_url.cred = getpass.getpass() try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) +except ldap.INVALID_CREDENTIALS as e: + print('Simple bind failed:',str(e)) sys.exit(1) st_ctrl = SessionTrackingControl( diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 1177d18d..e4c62e8b 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -7,16 +7,15 @@ Notes: The bound user needs read access to the attributes entryDN and entryCSN. - -This needs the following software: -Python -pyasn1 0.1.4+ -pyasn1-modules -python-ldap 2.4.10+ """ +from __future__ import print_function # Import modules from Python standard lib -import shelve,signal,time,sys,logging +import logging +import shelve +import signal +import sys +import time # Import the python-ldap modules import ldap @@ -25,28 +24,34 @@ from ldap.ldapobject import ReconnectLDAPObject from ldap.syncrepl import SyncreplConsumer +logger = logging.getLogger('syncrepl') +logger.setLevel(logging.DEBUG) +logger.addHandler(logging.StreamHandler()) # Global state watcher_running = True ldap_connection = False -class SyncReplConsumer(ReconnectLDAPObject, SyncreplConsumer): +class SyncReplClient(ReconnectLDAPObject, SyncreplConsumer): """ - Syncrepl Consumer interface + Syncrepl Consumer Client """ def __init__(self, db_path, *args, **kwargs): # Initialise the LDAP Connection first ldap.ldapobject.ReconnectLDAPObject.__init__(self, *args, **kwargs) # Now prepare the data store - self.__data = shelve.open(db_path, 'c') + if db_path: + self.__data = shelve.open(db_path, 'c') + else: + self.__data = dict() # We need this for later internal use self.__presentUUIDs = dict() def close_db(self): - # Close the data store properly to avoid corruption - self.__data.close() + # Close the data store properly to avoid corruption + self.__data.close() def syncrepl_get_cookie(self): if 'cookie' in self.__data: @@ -55,7 +60,8 @@ def syncrepl_get_cookie(self): def syncrepl_set_cookie(self,cookie): self.__data['cookie'] = cookie - def syncrepl_entry(self,dn,attributes,uuid): + def syncrepl_entry(self, dn, attributes, uuid): + logger.debug('dn=%r attributes=%r uuid=%r', dn, attributes, uuid) # First we determine the type of change we have here # (and store away the previous data for later if needed) previous_attributes = dict() @@ -69,18 +75,18 @@ def syncrepl_entry(self,dn,attributes,uuid): attributes['dn'] = dn self.__data[uuid] = attributes # Debugging - print 'Detected', change_type, 'of entry:', dn + logger.debug('Detected %s of entry %r', change_type, dn) # If we have a cookie then this is not our first time being run, # so it must be a change if 'ldap_cookie' in self.__data: - self.perform_application_sync(dn, attributes, previous_attributes) + self.perform_application_sync(dn, attributes, previous_attributes) def syncrepl_delete(self,uuids): # Make sure we know about the UUID being deleted, just in case... uuids = [uuid for uuid in uuids if uuid in self.__data] # Delete all the UUID values we know of for uuid in uuids: - print 'Detected deletion of entry:', self.__data[uuid]['dn'] + logger.debug('Detected deletion of entry %r', self.__data[uuid]['dn']) del self.__data[uuid] def syncrepl_present(self,uuids,refreshDeletes=False): @@ -105,10 +111,10 @@ def syncrepl_present(self,uuids,refreshDeletes=False): self.__presentUUIDs[uuid] = True def syncrepl_refreshdone(self): - print 'Initial synchronization is now done, persist phase begins' + logger.info('Initial synchronization is now done, persist phase begins') def perform_application_sync(self,dn,attributes,previous_attributes): - print 'Performing application sync for:', dn + logger.info('Performing application sync for %r', dn) return True @@ -116,67 +122,69 @@ def perform_application_sync(self,dn,attributes,previous_attributes): def commenceShutdown(signum, stack): # Declare the needed global variables global watcher_running, ldap_connection - print 'Shutting down!' + logger.warn('Shutting down!') # We are no longer running watcher_running = False # Tear down the server connection - if( ldap_connection ): - ldap_connection.close_db() - ldap_connection.unbind_s() - del ldap_connection + if ldap_connection: + ldap_connection.close_db() + ldap_connection.unbind_s() + del ldap_connection # Shutdown sys.exit(0) # Time to actually begin execution # Install our signal handlers -signal.signal(signal.SIGTERM,commenceShutdown) -signal.signal(signal.SIGINT,commenceShutdown) +signal.signal(signal.SIGTERM, commenceShutdown) +signal.signal(signal.SIGINT, commenceShutdown) try: - ldap_url = ldapurl.LDAPUrl(sys.argv[1]) - database_path = sys.argv[2] + ldap_url = ldapurl.LDAPUrl(sys.argv[1]) + database_path = sys.argv[2] except IndexError,e: - print 'Usage:' - print sys.argv[0], ' ' - print sys.argv[0], '\'ldap://127.0.0.1/cn=users,dc=test'\ - '?*'\ - '?sub'\ - '?(objectClass=*)'\ - '?bindname=uid=admin%2ccn=users%2cdc=test,'\ - 'X-BINDPW=password\' db.shelve' + print ( + 'Usage:\n' + '{script_name} \n' + '{script_name} "ldap://127.0.0.1/cn=users,dc=test' + '?*' + '?sub' + '?(objectClass=*)' + '?bindname=uid=admin%2ccn=users%2cdc=test,' + 'X-BINDPW=password" db.shelve' + ).format(script_name=sys.argv[0]) + sys.exit(1) +except ValueError as e: + print('Error parsing command-line arguments:',str(e)) sys.exit(1) -except ValueError,e: - print 'Error parsing command-line arguments:',str(e) - sys.exit(1) while watcher_running: - print 'Connecting to LDAP server now...' + logger.info('Connecting to %s now...', ldap_url.initializeUrl()) # Prepare the LDAP server connection (triggers the connection as well) - ldap_connection = SyncReplConsumer(database_path, ldap_url.initializeUrl()) + ldap_connection = SyncReplClient(database_path, ldap_url.initializeUrl()) # Now we login to the LDAP server try: ldap_connection.simple_bind_s(ldap_url.who, ldap_url.cred) - except ldap.INVALID_CREDENTIALS, e: - print 'Login to LDAP server failed: ', str(e) + except ldap.INVALID_CREDENTIALS as err: + logger.error('Login to LDAP server failed: %s', err) sys.exit(1) except ldap.SERVER_DOWN: - print 'LDAP server is down, going to retry.' + logger.warn('LDAP server is down, going to retry.') time.sleep(5) continue # Commence the syncing - print 'Commencing sync process' + logger.debug('Commencing sync process') ldap_search = ldap_connection.syncrepl_search( - ldap_url.dn or '', - ldap_url.scope or ldap.SCOPE_SUBTREE, - mode = 'refreshAndPersist', - attrlist=ldap_url.attrs, - filterstr = ldap_url.filterstr or '(objectClass=*)' + ldap_url.dn or '', + ldap_url.scope or ldap.SCOPE_SUBTREE, + mode = 'refreshAndPersist', + attrlist=ldap_url.attrs, + filterstr = ldap_url.filterstr or '(objectClass=*)' ) try: @@ -185,10 +193,9 @@ def commenceShutdown(signum, stack): except KeyboardInterrupt: # User asked to exit commenceShutdown(None, None) - pass - except Exception, e: + except Exception as err: # Handle any exception if watcher_running: - print 'Encountered a problem, going to retry. Error:', str(e) + logger.exception('Unhandled exception, going to retry: %s', err) + logger.info('Going to retry after 5 secs') time.sleep(5) - pass diff --git a/Demo/rename.py b/Demo/rename.py index 1bb6fd70..91d7528f 100644 --- a/Demo/rename.py +++ b/Demo/rename.py @@ -1,10 +1,11 @@ +from __future__ import print_function import ldap from getpass import getpass # Create LDAPObject instance l = ldap.initialize('ldap://localhost:1389',trace_level=1) -print 'Password:' +print('Password:') cred = getpass() try: @@ -15,7 +16,7 @@ # Try a bind to provoke failure if protocol version is not supported l.bind_s('cn=root,dc=stroeder,dc=com',cred,ldap.AUTH_SIMPLE) - print 'Using rename_s():' + print('Using rename_s():') l.rename_s( 'uid=fred,ou=Unstructured testing tree,dc=stroeder,dc=com', diff --git a/Demo/resiter.py b/Demo/resiter.py index 33945248..d6796baf 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -3,10 +3,8 @@ written by Michael Stroeder See http://www.python-ldap.org for details. - -Python compability note: -Requires Python 2.3+ """ +from __future__ import print_function import ldap,ldap.resiter @@ -19,6 +17,6 @@ class LDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor): result_iter = l.allresults(msgid) for result_type,result_list,result_msgid,result_serverctrls in result_iter: - print result_type,result_list,result_msgid,result_serverctrls + print(result_type,result_list,result_msgid,result_serverctrls) l.unbind_s() diff --git a/Demo/sasl_bind.py b/Demo/sasl_bind.py index 05af6527..667221c1 100644 --- a/Demo/sasl_bind.py +++ b/Demo/sasl_bind.py @@ -1,5 +1,6 @@ # For documentation, see comments in Module/LDAPObject.c and the # ldap.sasl module documentation. +from __future__ import print_function import ldap,ldap.sasl @@ -60,23 +61,23 @@ ), ]: sasl_auth = ldap.sasl.sasl(sasl_cb_value_dict,sasl_mech) - print 20*'*',sasl_auth.mech,20*'*' + print(20*'*',sasl_auth.mech,20*'*') # Open the LDAP connection l = ldap.initialize(ldap_uri,trace_level=0) # Set protocol version to LDAPv3 to enable SASL bind! l.protocol_version = 3 try: l.sasl_interactive_bind_s("", sasl_auth) - except ldap.LDAPError,e: - print 'Error using SASL mechanism',sasl_auth.mech,str(e) + except ldap.LDAPError as e: + print('Error using SASL mechanism',sasl_auth.mech,str(e)) else: - print 'Sucessfully bound using SASL mechanism:',sasl_auth.mech + print('Sucessfully bound using SASL mechanism:',sasl_auth.mech) try: - print 'Result of Who Am I? ext. op:',repr(l.whoami_s()) - except ldap.LDAPError,e: - print 'Error using SASL mechanism',sasl_auth.mech,str(e) + print('Result of Who Am I? ext. op:',repr(l.whoami_s())) + except ldap.LDAPError as e: + print('Error using SASL mechanism',sasl_auth.mech,str(e)) try: - print 'OPT_X_SASL_USERNAME',repr(l.get_option(ldap.OPT_X_SASL_USERNAME)) + print('OPT_X_SASL_USERNAME',repr(l.get_option(ldap.OPT_X_SASL_USERNAME))) except AttributeError: pass diff --git a/Demo/schema.py b/Demo/schema.py index c8103f3f..4d350f02 100644 --- a/Demo/schema.py +++ b/Demo/schema.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys,ldap,ldap.schema schema_attrs = ldap.schema.SCHEMA_ATTRS @@ -9,55 +10,55 @@ subschemasubentry_dn,schema = ldap.schema.urlfetch(sys.argv[-1]) if subschemasubentry_dn is None: - print 'No sub schema sub entry found!' + print('No sub schema sub entry found!') sys.exit(1) if schema.non_unique_oids: - print '*** Schema errors ***' - print 'non-unique OIDs:\n','\r\n'.join(schema.non_unique_oids) + print('*** Schema errors ***') + print('non-unique OIDs:\n','\r\n'.join(schema.non_unique_oids)) -print '*** Schema from',repr(subschemasubentry_dn) +print('*** Schema from',repr(subschemasubentry_dn)) # Display schema for attr_type,schema_class in ldap.schema.SCHEMA_CLASS_MAPPING.items(): - print '*'*20,attr_type,'*'*20 + print('*'*20,attr_type,'*'*20) for element_id in schema.listall(schema_class): se_orig = schema.get_obj(schema_class,element_id) - print attr_type,str(se_orig) -print '*** Testing object class inetOrgPerson ***' + print(attr_type,str(se_orig)) +print('*** Testing object class inetOrgPerson ***') drink = schema.get_obj(ldap.schema.AttributeType,'favouriteDrink') if not drink is None: - print '*** drink ***' - print 'drink.names',repr(drink.names) - print 'drink.collective',repr(drink.collective) + print('*** drink ***') + print('drink.names',repr(drink.names)) + print('drink.collective',repr(drink.collective)) inetOrgPerson = schema.get_obj(ldap.schema.ObjectClass,'inetOrgPerson') if not inetOrgPerson is None: - print inetOrgPerson.must,inetOrgPerson.may + print(inetOrgPerson.must,inetOrgPerson.may) -print '*** person,organizationalPerson,inetOrgPerson ***' +print('*** person,organizationalPerson,inetOrgPerson ***') try: - print schema.attribute_types( + print(schema.attribute_types() ['person','organizationalPerson','inetOrgPerson'] ) - print schema.attribute_types( + print(schema.attribute_types() ['person','organizationalPerson','inetOrgPerson'], attr_type_filter = [ ('no_user_mod',[0]), ('usage',range(2)), ] ) -except KeyError,e: - print '***KeyError',str(e) +except KeyError as e: + print('***KeyError',str(e)) schema.ldap_entry() -print str(schema.get_obj(ldap.schema.MatchingRule,'2.5.13.0')) -print str(schema.get_obj(ldap.schema.MatchingRuleUse,'2.5.13.0')) +print(str(schema.get_obj(ldap.schema.MatchingRule,'2.5.13.0'))) +print(str(schema.get_obj(ldap.schema.MatchingRuleUse,'2.5.13.0'))) -print str(schema.get_obj(ldap.schema.AttributeType,'name')) -print str(schema.get_inheritedobj(ldap.schema.AttributeType,'cn',['syntax','equality','substr','ordering'])) +print(str(schema.get_obj(ldap.schema.AttributeType,'name'))) +print(str(schema.get_inheritedobj(ldap.schema.AttributeType,'cn',['syntax','equality','substr','ordering']))) must_attr,may_attr = schema.attribute_types(['person','organizationalPerson','inetOrgPerson'],raise_keyerror=0) diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index 3b3a091e..79c8a837 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -4,6 +4,7 @@ Usage: schema_oc_tree.py [--html] [LDAP URL] """ +from __future__ import print_function import sys,getopt,ldap,ldap.schema @@ -14,11 +15,11 @@ def PrintSchemaTree(schema,se_class,se_tree,se_oid,level): """ASCII text output for console""" se_obj = schema.get_obj(se_class,se_oid) if se_obj!=None: - print '| '*(level-1)+'+---'*(level>0), \ + print('| '*(level-1)+'+---'*(level>0), \) ', '.join(se_obj.names), \ '(%s)' % se_obj.oid for sub_se_oid in se_tree[se_oid]: - print '| '*(level+1) + print('| '*(level+1)) PrintSchemaTree(schema,se_class,se_tree,sub_se_oid,level+1) @@ -26,17 +27,17 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): """HTML output for browser""" se_obj = schema.get_obj(se_class,se_oid) if se_obj!=None: - print """ + print("""
%s (%s)
%s - """ % (', '.join(se_obj.names),se_obj.oid,se_obj.desc) + """ % (', '.join(se_obj.names),se_obj.oid,se_obj.desc)) if se_tree[se_oid]: - print '
' + print('
') for sub_se_oid in se_tree[se_oid]: HTMLSchemaTree(schema,se_class,se_tree,sub_se_oid,level+1) - print '
' - print '
' + print('') + print('') ldap.set_option(ldap.OPT_DEBUG_LEVEL,0) @@ -46,13 +47,13 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): subschemasubentry_dn,schema = ldap.schema.urlfetch(sys.argv[-1],ldap.trace_level) if subschemasubentry_dn is None: - print 'No sub schema sub entry found!' + print('No sub schema sub entry found!') sys.exit(1) try: options,args=getopt.getopt(sys.argv[1:],'',['html']) -except getopt.error,e: - print 'Error: %s\nUsage: schema_oc_tree.py [--html] [LDAP URL]' +except getopt.error: + print('Error: %s\nUsage: schema_oc_tree.py [--html] [LDAP URL]') html_output = options and options[0][0]=='--html' @@ -60,41 +61,41 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): at_tree = schema.tree(ldap.schema.AttributeType) #for k,v in oc_tree.items(): -# print k,'->',v +# print(k,'->',v) #for k,v in at_tree.items(): -# print k,'->',v +# print(k,'->',v) if html_output: - print """ + print(""" Object class tree

Object class tree

-""" +""") HTMLSchemaTree(schema,ldap.schema.ObjectClass,oc_tree,'2.5.6.0',0) - print """
+ print("""

Attribute type tree

-""" +""") for a in schema.listall(ldap.schema.AttributeType): if at_tree[a]: HTMLSchemaTree(schema,ldap.schema.AttributeType,at_tree,a,0) print - print """
+ print(""" -""" +""") else: - print '*** Object class tree ***\n' + print('*** Object class tree ***\n') print PrintSchemaTree(schema,ldap.schema.ObjectClass,oc_tree,'2.5.6.0',0) - print '\n*** Attribute types tree ***\n' + print('\n*** Attribute types tree ***\n') PrintSchemaTree(schema,ldap.schema.AttributeType,at_tree,'_',0) diff --git a/Demo/simple.py b/Demo/simple.py index e4cf4d0f..6004c9ef 100644 --- a/Demo/simple.py +++ b/Demo/simple.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys,getpass import ldap @@ -14,7 +15,7 @@ try: dn = "ou=CSEE,o=UQ,c=AU" - print "Adding", repr(dn) + print("Adding", repr(dn)) l.add_s(dn, [ ("objectclass",["organizationalUnit"]), @@ -32,7 +33,7 @@ # dn = "cn=David Leonard,ou=CSEE,o=UQ,c=AU" -print "Updating", repr(dn) +print("Updating", repr(dn)) try: l.delete_s(dn) @@ -100,7 +101,7 @@ _ldap.SCOPE_SUBTREE, "objectclass=*", ) -print res +print(res) l.unbind() diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index f8e7182a..aa88f67e 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -3,15 +3,15 @@ # # simple LDAP server browsing example # +from __future__ import print_function import ldap -import string from traceback import print_exc url = "ldap://ldap.openldap.org/" dn = "dc=openldap,dc=org" -print "Connecting to", url +print("Connecting to", url) l = ldap.initialize(url) l.bind_s("", "", ldap.AUTH_SIMPLE); @@ -31,18 +31,18 @@ try: if cmd == "?": - print "cd - change DN to " - print "cd - change DN to number of last 'ls'" - print "cd - - change to previous DN" - print "cd .. - change to one-level higher DN" - print "cd - change to root DN" - print "ls - list children of crrent DN" - print ". - show attributes of current DN" - print "/ - list descendents matching filter " - print "? - show this help" + print( "cd - change DN to ") + print( "cd - change DN to number of last 'ls'") + print( "cd - - change to previous DN") + print( "cd .. - change to one-level higher DN") + print( "cd - change to root DN") + print( "ls - list children of crrent DN") + print( ". - show attributes of current DN") + print( "/ - list descendents matching filter ") + print( "? - show this help") elif cmd == "ls": - print "Children of", `dn`, ":" + print("Children of", `dn`, ":") dnlist = [] # # List the children at one level down from the current dn @@ -59,7 +59,7 @@ shortname = name[:-len(dn)-2]+" +" else: shortname = name - print " %3d. %s" % (len(dnlist), shortname) + print(" %3d. %s" % (len(dnlist), shortname)) dnlist.append(name) elif cmd == "cd": @@ -71,8 +71,8 @@ if arg == '-': lastdn,dn = dn,lastdn elif arg == '..': - dn = string.join(ldap.explode_dn(dn)[1:], ",") - dn = string.strip(dn) + dn = ldap.explode_dn(dn)[1:].join(",") + dn = dn.strip() else: try: i = int(arg) @@ -80,7 +80,7 @@ godn = arg else: if dnlist is None: - print "do an ls first" + print("do an ls first") else: godn = dnlist[i] lastdn = dn @@ -94,10 +94,10 @@ # No attributes are listed, so the default is for # the client to receive all attributes on the DN. # - print "Attributes of", `dn`, ":" + print("Attributes of", `dn`, ":") for name,attrs in l.search_s(dn, ldap.SCOPE_BASE, "objectclass=*"): - print " %-24s" % name + print(" %-24s" % name) for k,vals in attrs.items(): for v in vals: if len(v) > 200: @@ -105,7 +105,7 @@ ("... (%d bytes)" % len(v)) else: v = `v` - print " %-12s: %s" % (k, v) + print(" %-12s: %s" % (k, v)) elif cmd.startswith("/"): # @@ -115,13 +115,13 @@ # that we're not interested in them. # expr = cmd[1:] - print "Descendents matching filter", `expr`, ":" + print("Descendents matching filter", `expr`, ":") for name,attrs in l.search_s(dn, ldap.SCOPE_SUBTREE, expr, []): - print " %24s", name + print(" %24s", name) else: - print "unknown command - try '?' for help" + print("unknown command - try '?' for help") except: print_exc() diff --git a/Doc/conf.py b/Doc/conf.py index c52b01d6..9c322de3 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -40,9 +40,9 @@ # other places throughout the built documents. # # The short X.Y version. -version = '2.4' +version = '2.5' # The full version, including alpha/beta/rc tags. -release = '2.4.45.0' +release = '2.5.2.0' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/Doc/dsml.rst b/Doc/dsml.rst deleted file mode 100644 index 8b9a4601..00000000 --- a/Doc/dsml.rst +++ /dev/null @@ -1,26 +0,0 @@ -*************************************** -:mod:`dsml` DSMLv1 parser and generator -*************************************** - -.. :py:module:: dsml - :synopsis: Parses and generates DSMLv1 files -.. moduleauthor:: python-ldap project (see https://www.python-ldap.org/) - - -This module parses and generates LDAP data in the format DSMLv1. It is -implemented in pure Python and does not rely on any non-standard modules. -Therefore it can be used stand-alone without the rest of the python-ldap -package. - - -.. autoclass:: dsml.DSMLWriter - :members: - -.. autoclass:: dsml.DSMLParser - :members: - - -.. _dsml-example: - -Example -======= diff --git a/Doc/index.rst b/Doc/index.rst index b93e9c1d..1f9bf6aa 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -31,10 +31,46 @@ Contents ldap-sasl.rst ldif.rst ldapurl.rst - dsml.rst slapdtest.rst + +********************* +Bytes/text management +********************* + +The LDAP protocol states that some fields (distinguised names, relative distinguished names, +attribute names, queries) be encoded in UTF-8; some other (mostly attribute *values*) **MAY** +contain any type of data, and thus be treated as bytes. + +In Python 2, ``python-ldap`` used bytes for all fields, including those guaranteed to be text. +In order to support Python 3, this distinction is made explicit. This is done +through the ``bytes_mode`` flag to ``ldap.initialize()``. + +When porting from ``python-ldap`` 2.x, users are advised to update their code to set ``bytes_mode=False`` +on calls to these methods. +Under Python 2, ``python-pyldap`` aggressively checks the type of provided arguments, and will raise a ``TypeError`` +for any invalid parameter. +However, if the ``bytes_mode`` kwarg isn't provided, ``pyldap`` will only +raise warnings. + +The typical usage is as follows; note that only the result's *values* are of the bytes type: + +.. code-block:: pycon + + >>> import ldap + >>> con = ldap.initialize('ldap://localhost:389', bytes_mode=False) + >>> con.simple_bind_s('login', 'secret_password') + >>> results = con.search_s('ou=people,dc=example,dc=org', ldap.SCOPE_SUBTREE, "(cn=Raphaël)") + >>> results + [ + ("cn=Raphaël,ou=people,dc=example,dc=org", { + 'cn': [b'Rapha\xc3\xabl'], + 'sn': [b'Barrois'], + }), + ] + + ****************** Indices and tables ****************** diff --git a/Doc/installing.rst b/Doc/installing.rst index 116ba6fe..bcc41be3 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -15,8 +15,8 @@ use the same one you plan to use python-ldap with. See further instructions for using DistUtils here: https://docs.python.org/2.7/install/index.html -Prerequisites -============= +Build prerequisites +=================== The following software packages are required to be installed on the local system when building python-ldap: @@ -28,11 +28,18 @@ on the local system when building python-ldap: - cyrus-sasl (optional): https://www.cyrusimap.org/sasl/ - Kerberos libs, MIT or heimdal (optional) -Furthermore some sub-modules of :py:mod:`ldap.controls` and :py:mod:`ldap.extop` -require :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. +Installation prerequisites +========================== + +Furthermore it is required that modules +:py:mod:`pyasn1` and :py:mod:`pyasn1_modules` +are installed. https://github.com/etingof/pyasn1 + https://pypi.python.org/pypi/pyasn1 + + https://pypi.python.org/pypi/pyasn1-modules setup.cfg ========= @@ -66,25 +73,25 @@ documentation of Python's DistUtils. Libs used --------- -.. data:: ldap -.. data:: ldap_r +.. data:noindex: ldap +.. data:noindex: ldap_r The LDAP protocol library of OpenLDAP. ldap_r is the reentrant version and should be preferred. -.. data:: lber +.. data:noindex: lber The BER encoder/decoder library of OpenLDAP. -.. data:: sasl2 +.. data:noindex: sasl2 The Cyrus-SASL library if needed and present during build -.. data:: ssl +.. data:noindex: ssl The SSL/TLS library of OpenSSL if needed and present during build -.. data:: crypto +.. data:noindex: crypto The basic cryptographic library of OpenSSL if needed and present during build diff --git a/Doc/ldap-async.rst b/Doc/ldap-async.rst index d1c80874..59a34f43 100644 --- a/Doc/ldap-async.rst +++ b/Doc/ldap-async.rst @@ -27,9 +27,6 @@ Classes .. autoclass:: ldap.async.LDIFWriter :members: -.. autoclass:: ldap.async.DSMLWriter - :members: - .. _ldap.async-example: Examples diff --git a/Doc/ldap-dn.rst b/Doc/ldap-dn.rst index d78f070c..c22a64c4 100644 --- a/Doc/ldap-dn.rst +++ b/Doc/ldap-dn.rst @@ -49,7 +49,7 @@ The :mod:`ldap.dn` module defines the following functions: function :func:`escape_dn_chars`. -.. function:: explode_dn(dn [, notypes=0[, flags=0]]) -> list +.. function:: explode_dn(dn [, notypes=False[, flags=0]]) -> list This function takes *dn* and breaks it up into its component parts. Each part is known as an RDN (Relative Distinguished Name). The optional *notypes* @@ -60,7 +60,7 @@ The :mod:`ldap.dn` module defines the following functions: deprecated. -.. function:: explode_rdn(rdn [, notypes=0[, flags=0]]) -> list +.. function:: explode_rdn(rdn [, notypes=False[, flags=0]]) -> list This function takes a (multi-valued) *rdn* and breaks it up into a list of characteristic attributes. The optional *notypes* parameter is used to specify @@ -85,26 +85,26 @@ Splitting a LDAPv3 DN to AVA level. Note that both examples have the same result but in the first example the non-ASCII chars are passed as is (byte buffer string) whereas in the second example the hex-encoded DN representation are passed to the function. ->>> ldap.dn.str2dn('cn=Michael Str\xc3\xb6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3) -[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] ->>> ldap.dn.str2dn('cn=Michael Str\C3\B6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3) -[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=Michael Str\xc3\xb6der,dc=example,dc=com',flags=ldap.DN_FORMAT_LDAPV3) +[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=Michael Str\C3\B6der,dc=example,dc=com',flags=ldap.DN_FORMAT_LDAPV3) +[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]] Splitting a LDAPv2 DN into RDN parts: ->>> ldap.dn.explode_dn('cn=Michael Stroeder;dc=stroeder;dc=com',flags=ldap.DN_FORMAT_LDAPV2) -['cn=Michael Stroeder', 'dc=stroeder', 'dc=com'] +>>> ldap.dn.explode_dn('cn=John Doe;dc=example;dc=com',flags=ldap.DN_FORMAT_LDAPV2) +['cn=John Doe', 'dc=example', 'dc=com'] Splitting a multi-valued RDN: ->>> ldap.dn.explode_rdn('cn=Michael Stroeder+mail=michael@stroeder.com',flags=ldap.DN_FORMAT_LDAPV2) -['cn=Michael Stroeder', 'mail=michael@stroeder.com'] +>>> ldap.dn.explode_rdn('cn=John Doe+mail=john.doe@example.com',flags=ldap.DN_FORMAT_LDAPV2) +['cn=John Doe', 'mail=john.doe@example.com'] Splitting a LDAPv3 DN with a multi-valued RDN into its AVA parts: ->>> ldap.dn.str2dn('cn=Michael Stroeder+mail=michael@stroeder.com,dc=stroeder,dc=com') -[[('cn', 'Michael Stroeder', 1), ('mail', 'michael@stroeder.com', 1)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=John Doe+mail=john.doe@example.com,dc=example,dc=com') +[[('cn', 'John Doe', 1), ('mail', 'john.doe@example.com', 1)], [('dc', 'example', 1)], [('dc', 'com', 1)]] diff --git a/Doc/ldap-resiter.rst b/Doc/ldap-resiter.rst index 0beabb8c..0e72e922 100644 --- a/Doc/ldap-resiter.rst +++ b/Doc/ldap-resiter.rst @@ -46,4 +46,4 @@ processing them in a for-loop. for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id): for dn,entry in res_data: # process dn and entry - print dn,entry['objectClass'] + print(dn,entry['objectClass']) diff --git a/Doc/ldap.rst b/Doc/ldap.rst index e983f647..5160f30b 100644 --- a/Doc/ldap.rst +++ b/Doc/ldap.rst @@ -1169,6 +1169,6 @@ subtree search. [('cn=Fred Feuerstein,ou=Testing,dc=stroeder,dc=de', {'cn': ['Fred Feuerstein']})] >>> r = l.search_s('ou=Testing,dc=stroeder,dc=de',ldap.SCOPE_SUBTREE,'(objectClass=*)',['cn','mail']) >>> for dn,entry in r: ->>> print 'Processing',repr(dn) +>>> print('Processing',repr(dn)) >>> handle_ldap_entry(entry) diff --git a/Lib/dsml.py b/Lib/dsml.py deleted file mode 100644 index 9a93b948..00000000 --- a/Lib/dsml.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -dsml - generate and parse DSMLv1 data -(see http://www.oasis-open.org/committees/dsml/) - -See https://www.python-ldap.org/ for details. - -Python compability note: -Tested with Python 2.0+. -""" - -__version__ = '2.4.45' - -import string,base64 - - -special_entities = ( - ('&','&'), - ('<','<'), - ('"','"'), - ("'",'''), -) - - -def replace_char(s): - for char,entity in special_entities: - s = string.replace(s,char,entity) - return s - - -class DSMLWriter: - """ - Class for writing LDAP entry records to a DSMLv1 file. - - Arguments: - - f - File object for output. - base64_attrs - Attribute types to be base64-encoded. - dsml_comment - Text placed in comment lines behind . - indent - String used for indentiation of next nested level. - """ - - def __init__( - self,f,base64_attrs=[],dsml_comment='',indent=' ' - ): - self._output_file = f - self._base64_attrs = {}.fromkeys(map(string.lower,base64_attrs)) - self._dsml_comment = dsml_comment - self._indent = indent - - def _needs_base64_encoding(self,attr_type,attr_value): - if self._base64_attrs: - return self._base64_attrs.has_key(string.lower(attr_type)) - else: - try: - unicode(attr_value,'utf-8') - except UnicodeError: - return 1 - else: - return 0 - - def writeHeader(self): - """ - Write the header - """ - self._output_file.write('\n'.join([ - '', - '', - '', - '%s\n' % (self._indent), - ]) - ) - if self._dsml_comment: - self._output_file.write('%s\n' % (self._indent)) - - def writeFooter(self): - """ - Write the footer - """ - self._output_file.write('%s\n' % (self._indent)) - self._output_file.write('\n') - - def unparse(self,dn,entry): - return self.writeRecord(dn,entry) - - def writeRecord(self,dn,entry): - """ - dn - string-representation of distinguished name - entry - dictionary holding the LDAP entry {attr:data} - """ - - # Write line dn: first - self._output_file.write( - '%s\n' % ( - self._indent*2,replace_char(dn) - ) - ) - - objectclasses = entry.get('objectclass',entry.get('objectClass',[])) - - self._output_file.write('%s\n' % (self._indent*3)) - for oc in objectclasses: - self._output_file.write('%s%s\n' % (self._indent*4,oc)) - self._output_file.write('%s\n' % (self._indent*3)) - - attr_types = entry.keys()[:] - try: - attr_types.remove('objectclass') - attr_types.remove('objectClass') - except ValueError: - pass - attr_types.sort() - for attr_type in attr_types: - self._output_file.write('%s\n' % (self._indent*3,attr_type)) - for attr_value_item in entry[attr_type]: - needs_base64_encoding = self._needs_base64_encoding( - attr_type,attr_value_item - ) - if needs_base64_encoding: - attr_value_item = base64.encodestring(attr_value_item) - else: - attr_value_item = replace_char(attr_value_item) - self._output_file.write('%s\n' % ( - self._indent*4, - ' encoding="base64"'*needs_base64_encoding - ) - ) - self._output_file.write('%s%s\n' % ( - self._indent*5, - attr_value_item - ) - ) - self._output_file.write('%s\n' % ( - self._indent*4, - ) - ) - self._output_file.write('%s\n' % (self._indent*3)) - self._output_file.write('%s\n' % (self._indent*2)) - return - - -try: - - import xml.sax,xml.sax.handler - -except ImportError: - pass - -else: - - class DSMLv1Handler(xml.sax.handler.ContentHandler): - """ - Content handler class for DSMLv1 - """ - - def __init__(self,parser_instance): - self._parser_instance = parser_instance - xml.sax.handler.ContentHandler.__init__(self) - - def startDocument(self): - pass - - def endDocument(self): - pass - - def startElement(self,raw_name,attrs): - assert raw_name.startswith(''),'Illegal name' - name = raw_name[5:] - if name=='dsml': - pass - elif name=='directory-entries': - self._parsing_entries = 1 - elif name=='entry': - self._dn = attrs['dn'] - self._entry = {} - elif name=='attr': - self._attr_type = attrs['name'].encode('utf-8') - self._attr_values = [] - elif name=='value': - self._attr_value = '' - self._base64_encoding = attrs.get('encoding','').lower()=='base64' - # Handle object class tags - elif name=='objectclass': - self._object_classes = [] - elif name=='oc-value': - self._oc_value = '' - # Unhandled tags - else: - raise ValueError,'Unknown tag %s' % (raw_name) - - def endElement(self,raw_name): - assert raw_name.startswith('dsml:'),'Illegal name' - name = raw_name[5:] - if name=='dsml': - pass - elif name=='directory-entries': - self._parsing_entries = 0 - elif name=='entry': - self._parser_instance.handle(self._dn,self._entry) - del self._dn - del self._entry - elif name=='attr': - self._entry[self._attr_type] = self._attr_values - del self._attr_type - del self._attr_values - elif name=='value': - if self._base64_encoding: - attr_value = base64.decodestring(self._attr_value.strip()) - else: - attr_value = self._attr_value.strip().encode('utf-8') - self._attr_values.append(attr_value) - del attr_value - del self._attr_value - del self._base64_encoding - # Handle object class tags - elif name=='objectclass': - self._entry['objectClass'] = self._object_classes - del self._object_classes - elif name=='oc-value': - self._object_classes.append(self._oc_value.strip().encode('utf-8')) - del self._oc_value - # Unhandled tags - else: - raise ValueError,'Unknown tag %s' % (raw_name) - - def characters(self,ch): - if self.__dict__.has_key('_oc_value'): - self._oc_value = self._oc_value + ch - elif self.__dict__.has_key('_attr_value'): - self._attr_value = self._attr_value + ch - else: - pass - - - class DSMLParser: - """ - Base class for a DSMLv1 parser. Applications should sub-class this - class and override method handle() to implement something meaningful. - - Public class attributes: - - records_read - Counter for records processed so far - - Arguments: - - input_file - File-object to read the DSMLv1 input from - ignored_attr_types - Attributes with these attribute type names will be ignored. - max_entries - If non-zero specifies the maximum number of entries to be - read from f. - line_sep - String used as line separator - """ - - def __init__( - self, - input_file, - ContentHandlerClass, - ignored_attr_types=None, - max_entries=0, - ): - self._input_file = input_file - self._max_entries = max_entries - self._ignored_attr_types = {}.fromkeys(map(string.lower,(ignored_attr_types or []))) - self._current_record = None,None - self.records_read = 0 - self._parser = xml.sax.make_parser() - self._parser.setFeature(xml.sax.handler.feature_namespaces,0) - content_handler = ContentHandlerClass(self) - self._parser.setContentHandler(content_handler) - - def handle(self,*args,**kwargs): - """ - Process a single DSMLv1 entry record. This method should be - implemented by applications using DSMLParser. - """ - import pprint - pprint.pprint(args) - pprint.pprint(kwargs) - - def parse(self): - """ - Continously read and parse DSML records - """ - self._parser.parse(self._input_file) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 30c0010e..dab97a67 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -6,7 +6,7 @@ # This is also the overall release version number -__version__ = '2.4.45' +from pkginfo import __version__, __author__, __license__ import sys @@ -17,8 +17,14 @@ _trace_file = sys.stderr _trace_stack_limit = None +from ldap.pkginfo import __version__ + import _ldap +assert _ldap.__version__==__version__, \ + ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) from _ldap import * +# call into libldap to initialize it right now +LIBLDAP_API_INFO = _ldap.get_option(_ldap.OPT_API_INFO) OPT_NAMES_DICT = {} for k,v in vars(_ldap).items(): @@ -80,9 +86,9 @@ def release(self): # Create module-wide lock for serializing all calls into underlying LDAP lib _ldap_module_lock = LDAPLock(desc='Module wide') -from functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs +from ldap.functions import open,initialize,init,get_option,set_option,escape_str,strf_secs,strp_secs -from ldapobject import NO_UNIQUE_ENTRY +from ldap.ldapobject import NO_UNIQUE_ENTRY from ldap.dn import explode_dn,explode_rdn,str2dn,dn2str del str2dn diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 2cbb4888..0dd4940c 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -2,26 +2,22 @@ ldap.async - handle async LDAP operations See https://www.python-ldap.org/ for details. - -Python compability note: -Tested on Python 2.0+ but should run on Python 1.5.x. """ import ldap from ldap import __version__ +SEARCH_RESULT_TYPES = set([ + ldap.RES_SEARCH_ENTRY, + ldap.RES_SEARCH_RESULT, + ldap.RES_SEARCH_REFERENCE, +]) -_searchResultTypes={ - ldap.RES_SEARCH_ENTRY:None, - ldap.RES_SEARCH_RESULT:None, - ldap.RES_SEARCH_REFERENCE:None, -} - -_entryResultTypes={ - ldap.RES_SEARCH_ENTRY:None, - ldap.RES_SEARCH_RESULT:None, -} +ENTRY_RESULT_TYPES = set([ + ldap.RES_SEARCH_ENTRY, + ldap.RES_SEARCH_RESULT, +]) class WrongResultType(Exception): @@ -137,8 +133,8 @@ def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1): self._afterFirstResult = 0 if not result_list: break - if not _searchResultTypes.has_key(result_type): - raise WrongResultType(result_type,_searchResultTypes.keys()) + if result_type not in SEARCH_RESULT_TYPES: + raise WrongResultType(result_type,SEARCH_RESULT_TYPES) # Loop over list of search results for result_item in result_list: if result_counter list + explode_dn(dn [, notypes=False [, flags=0]]) -> list This function takes a DN and breaks it up into its component parts. The notypes parameter is used to specify that only the component's @@ -90,9 +91,9 @@ def explode_dn(dn,notypes=0,flags=0): return rdn_list -def explode_rdn(rdn,notypes=0,flags=0): +def explode_rdn(rdn, notypes=False, flags=0): """ - explode_rdn(rdn [, notypes=0]) -> list + explode_rdn(rdn [, notypes=0 [, flags=0]]) -> list This function takes a RDN and breaks it up into its component parts if it is a multi-valued RDN. diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index ea763fac..b8870378 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -2,18 +2,6 @@ functions.py - wraps functions of module _ldap See https://www.python-ldap.org/ for details. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x -- functions should behave exactly the same like in _ldap - -Usage: -Directly imported by ldap/__init__.py. The symbols of _ldap are -overridden. - -Thread-lock: -Basically calls into the LDAP lib are serialized by the module-wide -lock _ldapmodule_lock. """ from ldap import __version__ @@ -65,7 +53,7 @@ def _ldap_function_call(lock,func,*args,**kwargs): finally: if lock: lock.release() - except LDAPError,e: + except LDAPError as e: if __debug__ and ldap._trace_level>=2: ldap._trace_file.write('=> LDAPError: %s\n' % (str(e))) raise @@ -74,7 +62,7 @@ def _ldap_function_call(lock,func,*args,**kwargs): return result -def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None): +def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None, bytes_mode=None): """ Return LDAPObject instance by opening LDAP connection to LDAP host specified by LDAP URL @@ -88,11 +76,13 @@ def initialize(uri,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None): trace_file File object where to write the trace output to. Default is to use stdout. + bytes_mode + Whether to enable "bytes_mode" for backwards compatibility under Py2. """ - return LDAPObject(uri,trace_level,trace_file,trace_stack_limit) + return LDAPObject(uri,trace_level,trace_file,trace_stack_limit,bytes_mode) -def open(host,port=389,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None): +def open(host,port=389,trace_level=0,trace_file=sys.stdout,trace_stack_limit=None,bytes_mode=None): """ Return LDAPObject instance by opening LDAP connection to specified LDAP host @@ -107,10 +97,12 @@ def open(host,port=389,trace_level=0,trace_file=sys.stdout,trace_stack_limit=Non trace_file File object where to write the trace output to. Default is to use stdout. + bytes_mode + Whether to enable "bytes_mode" for backwards compatibility under Py2. """ import warnings warnings.warn('ldap.open() is deprecated! Use ldap.initialize() instead.', DeprecationWarning,2) - return initialize('ldap://%s:%d' % (host,port),trace_level,trace_file,trace_stack_limit) + return initialize('ldap://%s:%d' % (host,port),trace_level,trace_file,trace_stack_limit,bytes_mode) init = open diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 70ecec7e..01f00448 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -2,28 +2,17 @@ ldapobject.py - wraps class _ldap.LDAPObject See https://www.python-ldap.org/ for details. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x -- LDAPObject class should be exactly the same like _ldap.LDAPObject - -Usage: -Directly imported by ldap/__init__.py. The symbols of _ldap are -overridden. - -Thread-lock: -Basically calls into the LDAP lib are serialized by the module-wide -lock self._ldap_object_lock. """ +from __future__ import unicode_literals + from os import strerror -from ldap import __version__ +from ldap.pkginfo import __version__, __author__, __license__ __all__ = [ 'LDAPObject', 'SimpleLDAPObject', - 'NonblockingLDAPObject', 'ReconnectLDAPObject', ] @@ -33,13 +22,20 @@ import traceback import sys,time,pprint,_ldap,ldap,ldap.sasl,ldap.functions +import warnings from ldap.schema import SCHEMA_ATTRS from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples from ldap.extop import ExtendedRequest,ExtendedResponse +from ldap.compat import reraise from ldap import LDAPError +PY2 = bool(sys.version_info[0] <= 2) +if PY2: + text_type = unicode +else: + text_type = str class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT): """ @@ -67,7 +63,7 @@ class SimpleLDAPObject: def __init__( self,uri, - trace_level=0,trace_file=None,trace_stack_limit=5 + trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None ): self._trace_level = trace_level self._trace_file = trace_file or sys.stdout @@ -78,6 +74,186 @@ def __init__( self.timeout = -1 self.protocol_version = ldap.VERSION3 + # Bytes mode + # ---------- + + # By default, raise a TypeError when receiving invalid args + self.bytes_mode_hardfail = True + if bytes_mode is None and PY2: + warnings.warn( + "Under Python 2, python-ldap uses bytes by default. " + "This will be removed in Python 3 (no bytes for DN/RDN/field names). " + "Please call initialize(..., bytes_mode=False) explicitly.", + BytesWarning, + stacklevel=2, + ) + bytes_mode = True + # Disable hard failure when running in backwards compatibility mode. + self.bytes_mode_hardfail = False + elif bytes_mode and not PY2: + raise ValueError("bytes_mode is *not* supported under Python 3.") + # On by default on Py2, off on Py3. + self.bytes_mode = bytes_mode + + def _bytesify_input(self, value): + """Adapt a value following bytes_mode in Python 2. + + In Python 3, returns the original value unmodified. + + With bytes_mode ON, takes bytes or None and returns bytes or None. + With bytes_mode OFF, takes unicode or None and returns bytes or None. + + This function should be applied on all text inputs (distinguished names + and attribute names in modlists) to convert them to the bytes expected + by the C bindings. + """ + if not PY2: + return value + + if value is None: + return value + elif self.bytes_mode: + if isinstance(value, bytes): + return value + else: + if self.bytes_mode_hardfail: + raise TypeError("All provided fields *must* be bytes when bytes mode is on; got %r" % (value,)) + else: + warnings.warn( + "Received non-bytes value %r with default (disabled) bytes mode; please choose an explicit " + "option for bytes_mode on your LDAP connection" % (value,), + BytesWarning, + stacklevel=6, + ) + return value.encode('utf-8') + else: + if not isinstance(value, text_type): + raise TypeError("All provided fields *must* be text when bytes mode is off; got %r" % (value,)) + assert not isinstance(value, bytes) + return value.encode('utf-8') + + def _bytesify_inputs(self, *values): + """Adapt values following bytes_mode. + + Applies _bytesify_input on each arg. + + Usage: + >>> a, b, c = self._bytesify_inputs(a, b, c) + """ + if not PY2: + return values + return ( + self._bytesify_input(value) + for value in values + ) + + def _bytesify_modlist(self, modlist, with_opcode): + """Adapt a modlist according to bytes_mode. + + A modlist is a tuple of (op, attr, value), where: + - With bytes_mode ON, attr is checked to be bytes + - With bytes_mode OFF, attr is converted from unicode to bytes + - value is *always* bytes + """ + if not PY2: + return modlist + if with_opcode: + return tuple( + (op, self._bytesify_input(attr), val) + for op, attr, val in modlist + ) + else: + return tuple( + (self._bytesify_input(attr), val) + for attr, val in modlist + ) + + def _unbytesify_text_value(self, value): + """Adapt a 'known text, UTF-8 encoded' returned value following bytes_mode. + + With bytes_mode ON, takes bytes or None and returns bytes or None. + With bytes_mode OFF, takes bytes or None and returns unicode or None. + + This function should only be applied on field *values*; distinguished names + or field *names* are already natively handled in result4. + """ + if value is None: + return value + + # Preserve logic of assertions only under Python 2 + if PY2: + assert isinstance(value, bytes), "Expected bytes value, got text instead (%r)" % (value,) + + if self.bytes_mode: + return value + else: + return value.decode('utf-8') + + def _maybe_rebytesify_text(self, value): + """Re-encodes text to bytes if needed by bytes_mode. + + Takes unicode (and checks for it), and returns: + - bytes under bytes_mode + - unicode otherwise. + """ + if not PY2: + return value + + if value is None: + return value + + assert isinstance(value, text_type), "Should return text, got bytes instead (%r)" % (value,) + if not self.bytes_mode: + return value + else: + return value.encode('utf-8') + + def _bytesify_result_value(self, result_value): + """Applies bytes_mode to a result value. + + Such a value can either be: + - a dict mapping an attribute name to its list of values + (where attribute names are unicode and values bytes) + - a list of referals (which are unicode) + """ + if not PY2: + return result_value + if hasattr(result_value, 'items'): + # It's a attribute_name: [values] dict + return dict( + (self._maybe_rebytesify_text(key), value) + for (key, value) in result_value.items() + ) + elif isinstance(result_value, bytes): + return result_value + else: + # It's a list of referals + # Example value: + # [u'ldap://DomainDnsZones.xxxx.root.local/DC=DomainDnsZones,DC=xxxx,DC=root,DC=local'] + return [self._maybe_rebytesify_text(referal) for referal in result_value] + + def _bytesify_results(self, results, with_ctrls=False): + """Converts a "results" object according to bytes_mode. + + Takes: + - a list of (dn, {field: [values]}) if with_ctrls is False + - a list of (dn, {field: [values]}, ctrls) if with_ctrls is True + + And, if bytes_mode is on, converts dn and fields to bytes. + """ + if not PY2: + return results + if with_ctrls: + return [ + (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields), ctrls) + for (dn, fields, ctrls) in results + ] + else: + return [ + (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields)) + for (dn, fields) in results + ] + def _ldap_lock(self,desc=''): if ldap.LIBLDAP_R: return ldap.LDAPLock(desc='%s within %s' %(desc,repr(self))) @@ -109,16 +285,19 @@ def _ldap_call(self,func,*args,**kwargs): diagnostic_message_success = self._l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE) finally: self._ldap_object_lock.release() - except LDAPError, e: + except LDAPError as e: exc_type,exc_value,exc_traceback = sys.exc_info() try: - if not e.args[0].has_key('info') and e.args[0].has_key('errno'): + if 'info' not in e.args[0] and 'errno' in e.args[0]: e.args[0]['info'] = strerror(e.args[0]['errno']) except IndexError: pass if __debug__ and self._trace_level>=2: self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e))) - raise exc_type,exc_value,exc_traceback + try: + reraise(exc_type, exc_value, exc_traceback) + finally: + exc_type = exc_value = exc_traceback = None else: if __debug__ and self._trace_level>=2: if not diagnostic_message_success is None: @@ -127,20 +306,20 @@ def _ldap_call(self,func,*args,**kwargs): return result def __setattr__(self,name,value): - if self.CLASSATTR_OPTION_MAPPING.has_key(name): + if name in self.CLASSATTR_OPTION_MAPPING: self.set_option(self.CLASSATTR_OPTION_MAPPING[name],value) else: self.__dict__[name] = value def __getattr__(self,name): - if self.CLASSATTR_OPTION_MAPPING.has_key(name): + if name in self.CLASSATTR_OPTION_MAPPING: return self.get_option(self.CLASSATTR_OPTION_MAPPING[name]) - elif self.__dict__.has_key(name): + elif name in self.__dict__: return self.__dict__[name] else: - raise AttributeError,'%s has no attribute %s' % ( + raise AttributeError('%s has no attribute %s' % ( self.__class__.__name__,repr(name) - ) + )) def fileno(self): """ @@ -194,6 +373,8 @@ def add_ext(self,dn,modlist,serverctrls=None,clientctrls=None): The parameter modlist is similar to the one passed to modify(), except that no operation integer need be included in the tuples. """ + dn = self._bytesify_input(dn) + modlist = self._bytesify_modlist(modlist, with_opcode=False) return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None): @@ -218,6 +399,7 @@ def simple_bind(self,who='',cred='',serverctrls=None,clientctrls=None): """ simple_bind([who='' [,cred='']]) -> int """ + who, cred = self._bytesify_inputs(who, cred) return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def simple_bind_s(self,who='',cred='',serverctrls=None,clientctrls=None): @@ -294,17 +476,20 @@ def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None): A design bug in the library prevents value from containing nul characters. """ + dn, attr = self._bytesify_inputs(dn, attr) return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls) try: - resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout) + ldap_res = self.result3(msgid,all=1,timeout=self.timeout) except ldap.COMPARE_TRUE: return 1 except ldap.COMPARE_FALSE: return 0 - return None + raise ldap.PROTOCOL_ERROR( + 'Compare operation returned wrong result: %r' % (ldap_res) + ) def compare(self,dn,attr,value): return self.compare_ext(dn,attr,value,None,None) @@ -322,6 +507,7 @@ def delete_ext(self,dn,serverctrls=None,clientctrls=None): form returns the message id of the initiated request, and the result can be obtained from a subsequent call to result(). """ + dn = self._bytesify_input(dn) return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def delete_ext_s(self,dn,serverctrls=None,clientctrls=None): @@ -370,6 +556,8 @@ def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None): """ modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int """ + dn = self._bytesify_input(dn) + modlist = self._bytesify_modlist(modlist, with_opcode=True) return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None): @@ -423,6 +611,7 @@ def modrdn_s(self,dn,newrdn,delold=1): return self.rename_s(dn,newrdn,None,delold) def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None): + user, oldpw, newpw = self._bytesify_inputs(user, oldpw, newpw) return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None): @@ -444,6 +633,7 @@ def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls This actually corresponds to the rename* routines in the LDAP-EXT C API library. """ + dn, newrdn, newsuperior = self._bytesify_inputs(dn, newrdn, newsuperior) return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls)) def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None): @@ -532,6 +722,8 @@ def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermedi if add_ctrls: resp_data = [ (t,r,DecodeControlTuples(c,resp_ctrl_classes)) for t,r,c in resp_data ] decoded_resp_ctrls = DecodeControlTuples(resp_ctrls,resp_ctrl_classes) + if resp_data is not None: + resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls) return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0): @@ -579,6 +771,9 @@ def search_ext(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrson The amount of search results retrieved can be limited with the sizelimit parameter if non-zero. """ + base, filterstr = self._bytesify_inputs(base, filterstr) + if attrlist is not None: + attrlist = tuple(self._bytesify_inputs(*attrlist)) return self._ldap_call( self._l.search_ext, base,scope,filterstr, @@ -672,6 +867,8 @@ def search_subschemasubentry_s(self,dn=''): None as result indicates that the DN of the sub schema sub entry could not be determined. + + Returns: None or text/bytes depending on bytes_mode. """ try: r = self.search_s( @@ -693,7 +890,9 @@ def search_subschemasubentry_s(self,dn=''): # If dn was already root DSE we can return here return None else: - return search_subschemasubentry_dn + # With legacy bytes mode, return bytes; otherwise, since this is a DN, + # RFCs impose that the field value *can* be decoded to UTF-8. + return self._unbytesify_text_value(search_subschemasubentry_dn) except IndexError: return None @@ -772,40 +971,6 @@ def get_naming_contexts(self): ).get('namingContexts', []) -class NonblockingLDAPObject(SimpleLDAPObject): - - def __init__(self,uri,trace_level=0,trace_file=None,result_timeout=-1): - self._result_timeout = result_timeout - SimpleLDAPObject.__init__(self,uri,trace_level,trace_file) - - def result(self,msgid=ldap.RES_ANY,all=1,timeout=-1): - """ - """ - ldap_result = self._ldap_call(self._l.result,msgid,0,self._result_timeout) - if not all: - return ldap_result - start_time = time.time() - all_results = [] - while all: - while ldap_result[0] is None: - if (timeout>=0) and (time.time()-start_time>timeout): - self._ldap_call(self._l.abandon,msgid) - raise ldap.TIMEOUT( - "LDAP time limit (%d secs) exceeded." % (timeout) - ) - time.sleep(0.00001) - ldap_result = self._ldap_call(self._l.result,msgid,0,self._result_timeout) - if ldap_result[1] is None: - break - all_results.extend(ldap_result[1]) - ldap_result = None,None - return all_results - - def search_st(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,timeout=-1): - msgid = self.search(base,scope,filterstr,attrlist,attrsonly) - return self.result(msgid,all=1,timeout=timeout) - - class ReconnectLDAPObject(SimpleLDAPObject): """ In case of server failure (ldap.SERVER_DOWN) the implementations @@ -819,16 +984,17 @@ class ReconnectLDAPObject(SimpleLDAPObject): application. """ - __transient_attrs__ = { - '_l':None, - '_ldap_object_lock':None, - '_trace_file':None, - '_reconnect_lock':None, - } + __transient_attrs__ = set([ + '_l', + '_ldap_object_lock', + '_trace_file', + '_reconnect_lock', + '_last_bind', + ]) def __init__( self,uri, - trace_level=0,trace_file=None,trace_stack_limit=5, + trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None, retry_max=1,retry_delay=60.0 ): """ @@ -843,24 +1009,27 @@ def __init__( self._uri = uri self._options = [] self._last_bind = None - SimpleLDAPObject.__init__(self,uri,trace_level,trace_file,trace_stack_limit) + SimpleLDAPObject.__init__(self,uri,trace_level,trace_file,trace_stack_limit,bytes_mode) self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) self._retry_max = retry_max self._retry_delay = retry_delay self._start_tls = 0 - self._reconnects_done = 0L + self._reconnects_done = 0 def __getstate__(self): """return data representation for pickled object""" - d = {} - for k,v in self.__dict__.items(): - if not self.__transient_attrs__.has_key(k): - d[k] = v - return d + state = dict([ + (k,v) + for k,v in self.__dict__.items() + if k not in self.__transient_attrs__ + ]) + state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2] + return state def __setstate__(self,d): """set up the object from pickled data""" self.__dict__.update(d) + self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2] self._ldap_object_lock = self._ldap_lock() self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) self._trace_file = sys.stdout @@ -906,14 +1075,14 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): SimpleLDAPObject.start_tls_s(self) # Repeat last simple or SASL bind self._apply_last_bind() - except (ldap.SERVER_DOWN,ldap.TIMEOUT),e: + except (ldap.SERVER_DOWN,ldap.TIMEOUT): if __debug__ and self._trace_level>=1: self._trace_file.write('*** %s reconnect to %s failed\n' % ( counter_text,uri )) reconnect_counter = reconnect_counter-1 if not reconnect_counter: - raise e + raise if __debug__ and self._trace_level>=1: self._trace_file.write('=> delay %s...\n' % (retry_delay)) time.sleep(retry_delay) @@ -923,14 +1092,14 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % ( counter_text,uri )) - self._reconnects_done = self._reconnects_done + 1L + self._reconnects_done = self._reconnects_done + 1 break finally: self._reconnect_lock.release() return # reconnect() def _apply_method_s(self,func,*args,**kwargs): - if not self.__dict__.has_key('_l'): + if not hasattr(self,'_l'): self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) try: return func(self,*args,**kwargs) diff --git a/Lib/ldap/logger.py b/Lib/ldap/logger.py index d955c2e4..4db961e3 100644 --- a/Lib/ldap/logger.py +++ b/Lib/ldap/logger.py @@ -5,15 +5,15 @@ import logging -class logging_file_class: +class logging_file_class(object): - def __init__(self,logging_level): - self._logging_level = logging_level + def __init__(self, logging_level): + self._logging_level = logging_level - def write(self,msg): - logging.log(self._logging_level,msg[:-1]) + def write(self, msg): + logging.log(self._logging_level, msg[:-1]) - def flush(self): - return + def flush(self): + return logging_file_obj = logging_file_class(logging.DEBUG) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 0d1ac409..a853500d 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -2,43 +2,23 @@ ldap.modlist - create add/modify modlist's See https://www.python-ldap.org/ for details. - -Python compability note: -This module is known to work with Python 2.0+ but should work -with Python 1.5.2 as well. """ from ldap import __version__ -import string,ldap,ldap.cidict - - -def list_dict(l,case_insensitive=0): - """ - return a dictionary with all items of l being the keys of the dictionary - - If argument case_insensitive is non-zero ldap.cidict.cidict will be - used for case-insensitive string keys - """ - if case_insensitive: - d = ldap.cidict.cidict() - else: - d = {} - for i in l: - d[i]=None - return d +import ldap def addModlist(entry,ignore_attr_types=None): """Build modify list for call of method LDAPObject.add()""" - ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) + ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) modlist = [] for attrtype in entry.keys(): - if ignore_attr_types.has_key(string.lower(attrtype)): + if attrtype.lower() in ignore_attr_types: # This attribute type is ignored continue # Eliminate empty attr value strings in list - attrvaluelist = filter(lambda x:x!=None,entry[attrtype]) + attrvaluelist = [item for item in entry[attrtype] if item is not None] if attrvaluelist: modlist.append((attrtype,entry[attrtype])) return modlist # addModlist() @@ -66,22 +46,22 @@ def modifyModlist( List of attribute type names for which comparison will be made case-insensitive """ - ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) - case_ignore_attr_types = list_dict(map(string.lower,(case_ignore_attr_types or []))) + ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) + case_ignore_attr_types = set(map(str.lower,case_ignore_attr_types or [])) modlist = [] attrtype_lower_map = {} for a in old_entry.keys(): - attrtype_lower_map[string.lower(a)]=a + attrtype_lower_map[str.lower(a)]=a for attrtype in new_entry.keys(): - attrtype_lower = string.lower(attrtype) - if ignore_attr_types.has_key(attrtype_lower): + attrtype_lower = str.lower(attrtype) + if attrtype_lower in ignore_attr_types: # This attribute type is ignored continue # Filter away null-strings - new_value = filter(lambda x:x!=None,new_entry[attrtype]) - if attrtype_lower_map.has_key(attrtype_lower): + new_value = [item for item in new_entry[attrtype] if item is not None] + if attrtype_lower in attrtype_lower_map: old_value = old_entry.get(attrtype_lower_map[attrtype_lower],[]) - old_value = filter(lambda x:x!=None,old_value) + old_value = [item for item in old_value if item is not None] del attrtype_lower_map[attrtype_lower] else: old_value = [] @@ -92,20 +72,14 @@ def modifyModlist( # Replace existing attribute replace_attr_value = len(old_value)!=len(new_value) if not replace_attr_value: - case_insensitive = case_ignore_attr_types.has_key(attrtype_lower) - old_value_dict=list_dict(old_value,case_insensitive) - new_value_dict=list_dict(new_value,case_insensitive) - delete_values = [] - for v in old_value: - if not new_value_dict.has_key(v): - replace_attr_value = 1 - break - add_values = [] - if not replace_attr_value: - for v in new_value: - if not old_value_dict.has_key(v): - replace_attr_value = 1 - break + if attrtype_lower in case_ignore_attr_types: + norm_func = str.lower + old_value_set = set(map(str.lower,old_value)) + new_value_set = set(map(str.lower,new_value)) + else: + old_value_set = set(old_value) + new_value_set = set(new_value) + replace_attr_value = new_value_set != old_value_set if replace_attr_value: modlist.append((ldap.MOD_DELETE,attrtype,None)) modlist.append((ldap.MOD_ADD,attrtype,new_value)) @@ -116,7 +90,7 @@ def modifyModlist( # Remove all attributes of old_entry which are not present # in new_entry at all for a in attrtype_lower_map.keys(): - if ignore_attr_types.has_key(a): + if a in ignore_attr_types: # This attribute type is ignored continue attrtype = attrtype_lower_map[a] diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py new file mode 100644 index 00000000..da891b3d --- /dev/null +++ b/Lib/ldap/pkginfo.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +""" +meta attributes for packaging which does not import any dependencies +""" +__version__ = '2.5.2' +__author__ = u'python-ldap project' +__license__ = 'Python style' diff --git a/Lib/ldap/resiter.py b/Lib/ldap/resiter.py index d8c1368f..dc912eb3 100644 --- a/Lib/ldap/resiter.py +++ b/Lib/ldap/resiter.py @@ -2,26 +2,40 @@ ldap.resiter - processing LDAP results with iterators See https://www.python-ldap.org/ for details. - -Python compability note: -Requires Python 2.3+ """ +from ldap.pkginfo import __version__, __author__, __license__ -class ResultProcessor: - """ - Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes. - """ - def allresults(self,msgid,timeout=-1,add_ctrls=0): +class ResultProcessor: """ - Generator function which returns an iterator for processing all LDAP operation - results of the given msgid retrieved with LDAPObject.result3() -> 4-tuple + Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes. """ - result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls) - while result_type and result_list: - # Loop over list of search results - for result_item in result_list: - yield (result_type,result_list,result_msgid,result_serverctrls) - result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls) - return # allresults() + + def allresults(self, msgid, timeout=-1, add_ctrls=0): + """ + Generator function which returns an iterator for processing all LDAP operation + results of the given msgid like retrieved with LDAPObject.result3() -> 4-tuple + """ + result_type, result_list, result_msgid, result_serverctrls, _, _ = \ + self.result4( + msgid, + 0, + timeout, + add_ctrls=add_ctrls + ) + while result_type and result_list: + yield ( + result_type, + result_list, + result_msgid, + result_serverctrls + ) + result_type, result_list, result_msgid, result_serverctrls, _, _ = \ + self.result4( + msgid, + 0, + timeout, + add_ctrls=add_ctrls + ) + return # allresults() diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index 81438ccb..fa6f4f5c 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -10,44 +10,49 @@ LDAPObject's sasl_bind_s() method Implementing support for new sasl mechanism is very easy --- see the examples of digest_md5 and gssapi. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x """ from ldap import __version__ if __debug__: - # Tracing is only supported in debugging mode - import traceback - from ldap import _trace_level,_trace_file,_trace_stack_limit + # Tracing is only supported in debugging mode + from ldap import _trace_level, _trace_file + # These are the SASL callback id's , as defined in sasl.h -CB_USER = 0x4001 -CB_AUTHNAME = 0x4002 -CB_LANGUAGE = 0x4003 -CB_PASS = 0x4004 -CB_ECHOPROMPT = 0x4005 -CB_NOECHOPROMPT= 0x4006 -CB_GETREALM = 0x4008 +CB_USER = 0x4001 +CB_AUTHNAME = 0x4002 +CB_LANGUAGE = 0x4003 +CB_PASS = 0x4004 +CB_ECHOPROMPT = 0x4005 +CB_NOECHOPROMPT = 0x4006 +CB_GETREALM = 0x4008 + class sasl: - """This class handles SASL interactions for authentication. + """ + This class handles SASL interactions for authentication. If an instance of this class is passed to ldap's sasl_bind_s() method, the library will call its callback() method. For specific SASL authentication mechanisms, this method can be - overridden""" + overridden + """ - def __init__(self,cb_value_dict,mech): - """ The (generic) base class takes a cb_value_dictionary of + def __init__(self, cb_value_dict, mech): + """ + The (generic) base class takes a cb_value_dictionary of question-answer pairs. Questions are specified by the respective SASL callback id's. The mech argument is a string that specifies - the SASL mechaninsm to be uesd.""" + the SASL mechaninsm to be uesd. + """ self.cb_value_dict = cb_value_dict or {} + if not isinstance(mech, bytes): + mech = mech.encode('utf-8') self.mech = mech - def callback(self,cb_id,challenge,prompt,defresult): - """ The callback method will be called by the sasl_bind_s() + def callback(self, cb_id, challenge, prompt, defresult): + """ + The callback method will be called by the sasl_bind_s() method several times. Each time it will provide the id, which tells us what kind of information is requested (the CB_ ... constants above). The challenge might be a short (english) text @@ -60,50 +65,70 @@ def callback(self,cb_id,challenge,prompt,defresult): cb_value_dictionary. Note that the current callback interface is not very useful for writing generic sasl GUIs, which would need to know all the questions to ask, before the answers are returned to the sasl - lib (in contrast to one question at a time).""" + lib (in contrast to one question at a time). + + Unicode strings are always converted to bytes. + """ # The following print command might be useful for debugging # new sasl mechanisms. So it is left here - cb_result = self.cb_value_dict.get(cb_id,defresult) or '' + cb_result = self.cb_value_dict.get(cb_id, defresult) or '' if __debug__: - if _trace_level>=1: - _trace_file.write("*** id=%d, challenge=%s, prompt=%s, defresult=%s\n-> %s\n" % ( - cb_id, challenge, prompt, repr(defresult), repr(self.cb_value_dict.get(cb_result)) - )) + if _trace_level >= 1: + _trace_file.write("*** id=%d, challenge=%s, prompt=%s, defresult=%s\n-> %s\n" % ( + cb_id, + challenge, + prompt, + repr(defresult), + repr(self.cb_value_dict.get(cb_result)) + )) + if not isinstance(cb_result, bytes): + cb_result = cb_result.encode('utf-8') return cb_result class cram_md5(sasl): - """This class handles SASL CRAM-MD5 authentication.""" + """ + This class handles SASL CRAM-MD5 authentication. + """ - def __init__(self,authc_id, password, authz_id=""): - auth_dict = {CB_AUTHNAME:authc_id, CB_PASS:password, - CB_USER:authz_id} - sasl.__init__(self,auth_dict,"CRAM-MD5") + def __init__(self, authc_id, password, authz_id=""): + auth_dict = { + CB_AUTHNAME: authc_id, + CB_PASS: password, + CB_USER: authz_id, + } + sasl.__init__(self, auth_dict, "CRAM-MD5") class digest_md5(sasl): - """This class handles SASL DIGEST-MD5 authentication.""" + """ + This class handles SASL DIGEST-MD5 authentication. + """ - def __init__(self,authc_id, password, authz_id=""): - auth_dict = {CB_AUTHNAME:authc_id, CB_PASS:password, - CB_USER:authz_id} - sasl.__init__(self,auth_dict,"DIGEST-MD5") + def __init__(self, authc_id, password, authz_id=""): + auth_dict = { + CB_AUTHNAME: authc_id, + CB_PASS: password, + CB_USER: authz_id, + } + sasl.__init__(self, auth_dict, "DIGEST-MD5") class gssapi(sasl): - """This class handles SASL GSSAPI (i.e. Kerberos V) - authentication.""" + """ + This class handles SASL GSSAPI (i.e. Kerberos V) authentication. + """ - def __init__(self,authz_id=""): - sasl.__init__(self, {CB_USER:authz_id},"GSSAPI") + def __init__(self, authz_id=""): + sasl.__init__(self, {CB_USER: authz_id}, "GSSAPI") class external(sasl): - """This class handles SASL EXTERNAL authentication - (i.e. X.509 client certificate)""" - - def __init__(self,authz_id=""): - sasl.__init__(self, {CB_USER:authz_id},"EXTERNAL") - + """ + This class handles SASL EXTERNAL authentication + (i.e. X.509 client certificate) + """ + def __init__(self, authz_id=""): + sasl.__init__(self, {CB_USER: authz_id}, "EXTERNAL") diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 383705c3..c0391b4c 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -4,29 +4,24 @@ See https://www.python-ldap.org/ for details. """ -import UserDict,ldap.cidict +import sys -from ldap.schema.tokenizer import split_tokens,extract_tokens - -if __debug__: - from types import TupleType,StringType,IntType - try: - from types import BooleanType - except ImportError: - BooleanType = IntType +import ldap.cidict +from ldap.compat import IterableUserDict +from ldap.schema.tokenizer import split_tokens,extract_tokens -NOT_HUMAN_READABLE_LDAP_SYNTAXES = { - '1.3.6.1.4.1.1466.115.121.1.4':None, # Audio - '1.3.6.1.4.1.1466.115.121.1.5':None, # Binary - '1.3.6.1.4.1.1466.115.121.1.8':None, # Certificate - '1.3.6.1.4.1.1466.115.121.1.9':None, # Certificate List - '1.3.6.1.4.1.1466.115.121.1.10':None, # Certificate Pair - '1.3.6.1.4.1.1466.115.121.1.23':None, # G3 FAX - '1.3.6.1.4.1.1466.115.121.1.28':None, # JPEG - '1.3.6.1.4.1.1466.115.121.1.40':None, # Octet String - '1.3.6.1.4.1.1466.115.121.1.49':None, # Supported Algorithm -} +NOT_HUMAN_READABLE_LDAP_SYNTAXES = set([ + '1.3.6.1.4.1.1466.115.121.1.4', # Audio + '1.3.6.1.4.1.1466.115.121.1.5', # Binary + '1.3.6.1.4.1.1466.115.121.1.8', # Certificate + '1.3.6.1.4.1.1466.115.121.1.9', # Certificate List + '1.3.6.1.4.1.1466.115.121.1.10', # Certificate Pair + '1.3.6.1.4.1.1466.115.121.1.23', # G3 FAX + '1.3.6.1.4.1.1466.115.121.1.28', # JPEG + '1.3.6.1.4.1.1466.115.121.1.40', # Octet String + '1.3.6.1.4.1.1466.115.121.1.49', # Supported Algorithm +]) class SchemaElement: @@ -37,6 +32,7 @@ class SchemaElement: schema_element_str String which contains the schema element description to be parsed. + (Bytestrings are decoded using UTF-8) Class attributes: @@ -51,6 +47,8 @@ class SchemaElement: } def __init__(self,schema_element_str=None): + if sys.version_info >= (3, 0) and isinstance(schema_element_str, bytes): + schema_element_str = schema_element_str.decode('utf-8') if schema_element_str: l = split_tokens(schema_element_str) self.set_id(l[1]) @@ -68,7 +66,7 @@ def get_id(self): return self.oid def key_attr(self,key,value,quoted=0): - assert value is None or type(value)==StringType,TypeError("value has to be of StringType, was %s" % repr(value)) + assert value is None or type(value)==str,TypeError("value has to be of str, was %r" % value) if value: if quoted: return " %s '%s'" % (key,value.replace("'","\\'")) @@ -78,7 +76,7 @@ def key_attr(self,key,value,quoted=0): return "" def key_list(self,key,values,sep=' ',quoted=0): - assert type(values)==TupleType,TypeError("values has to be of ListType") + assert type(values)==tuple,TypeError("values has to be a tuple, was %r" % values) if not values: return '' if quoted: @@ -159,13 +157,6 @@ def _set_attrs(self,l,d): self.sup = ('top',) else: self.sup = d['SUP'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.sup)==TupleType - assert type(self.kind)==IntType - assert type(self.must)==TupleType - assert type(self.may)==TupleType return def __str__(self): @@ -279,21 +270,13 @@ def _set_attrs(self,l,d): self.syntax_len = None for i in l: if i.startswith("{") and i.endswith("}"): - self.syntax_len=long(i[1:-1]) + self.syntax_len = int(i[1:-1]) else: - self.syntax_len = long(syntax_len[:-1]) + self.syntax_len = int(syntax_len[:-1]) self.single_value = d['SINGLE-VALUE']!=None self.collective = d['COLLECTIVE']!=None self.no_user_mod = d['NO-USER-MODIFICATION']!=None self.usage = AttributeUsage.get(d['USAGE'][0],0) - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.sup)==TupleType,'attribute sup has type %s' % (type(self.sup)) - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.single_value)==BooleanType and (self.single_value==0 or self.single_value==1) - assert type(self.no_user_mod)==BooleanType and (self.no_user_mod==0 or self.no_user_mod==1) - assert self.syntax is None or type(self.syntax)==StringType - assert self.syntax_len is None or type(self.syntax_len)==type(0L) return def __str__(self): @@ -348,10 +331,9 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.x_subst = d['X-SUBST'][0] self.not_human_readable = \ - NOT_HUMAN_READABLE_LDAP_SYNTAXES.has_key(self.oid) or \ + self.oid in NOT_HUMAN_READABLE_LDAP_SYNTAXES or \ d['X-NOT-HUMAN-READABLE'][0]=='TRUE' self.x_binary_transfer_required = d['X-BINARY-TRANSFER-REQUIRED'][0]=='TRUE' - assert self.desc is None or type(self.desc)==StringType return def __str__(self): @@ -398,10 +380,6 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.obsolete = d['OBSOLETE']!=None self.syntax = d['SYNTAX'][0] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert self.syntax is None or type(self.syntax)==StringType return def __str__(self): @@ -448,10 +426,6 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.obsolete = d['OBSOLETE']!=None self.applies = d['APPLIES'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.applies)==TupleType return def __str__(self): @@ -515,13 +489,6 @@ def _set_attrs(self,l,d): self.must = d['MUST'] self.may = d['MAY'] self.nots = d['NOT'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.aux)==TupleType - assert type(self.must)==TupleType - assert type(self.may)==TupleType - assert type(self.nots)==TupleType return def __str__(self): @@ -582,11 +549,6 @@ def _set_attrs(self,l,d): self.obsolete = d['OBSOLETE']!=None self.form = d['FORM'][0] self.sup = d['SUP'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.form)==StringType - assert type(self.sup)==TupleType return def __str__(self): @@ -646,12 +608,6 @@ def _set_attrs(self,l,d): self.oc = d['OC'][0] self.must = d['MUST'] self.may = d['MAY'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.oc)==StringType - assert type(self.must)==TupleType - assert type(self.may)==TupleType return def __str__(self): @@ -665,7 +621,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(UserDict.UserDict): +class Entry(IterableUserDict): """ Schema-aware implementation of an LDAP entry class. @@ -678,7 +634,7 @@ def __init__(self,schema,dn,entry): self._attrtype2keytuple = {} self._s = schema self.dn = dn - UserDict.UserDict.__init__(self,{}) + IterableUserDict.IterableUserDict.__init__(self,{}) self.update(entry) def _at2key(self,nameoroid): @@ -702,8 +658,8 @@ def update(self,dict): for key in dict.keys(): self[key] = dict[key] - def __contains__(self,key): - return self.has_key(key) + def __contains__(self,nameoroid): + return self._at2key(nameoroid) in self.data def __getitem__(self,nameoroid): return self.data[self._at2key(nameoroid)] @@ -721,7 +677,7 @@ def __delitem__(self,nameoroid): def has_key(self,nameoroid): k = self._at2key(nameoroid) - return self.data.has_key(k) + return k in self.data def get(self,nameoroid,failobj): try: diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index a80f238d..2a42b4c0 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -8,8 +8,6 @@ from ldap.schema.models import * -from UserDict import UserDict - SCHEMA_CLASS_MAPPING = ldap.cidict.cidict() SCHEMA_ATTR_MAPPING = {} @@ -296,11 +294,12 @@ def get_structural_oc(self,oc_list): if oc_se and oc_se.kind==0: struct_ocs[oc_se.oid] = None result = None - struct_oc_list = struct_ocs.keys() + # Build a copy of the oid list, to be cleaned as we go. + struct_oc_list = list(struct_ocs) while struct_oc_list: oid = struct_oc_list.pop() for child_oid in oc_tree[oid]: - if struct_ocs.has_key(self.getoid(ObjectClass,child_oid)): + if self.getoid(ObjectClass,child_oid) in struct_ocs: break else: result = oid @@ -367,7 +366,7 @@ def attribute_types( object_class_oid = object_class_oids.pop(0) # Check whether the objectClass with this OID # has already been processed - if oid_cache.has_key(object_class_oid): + if object_class_oid in oid_cache: continue # Cache this OID as already being processed oid_cache[object_class_oid] = None @@ -419,20 +418,20 @@ def attribute_types( # Remove all mandantory attribute types from # optional attribute type list - for a in r_may.keys(): - if r_must.has_key(a): + for a in list(r_may.keys()): + if a in r_must: del r_may[a] # Apply attr_type_filter to results if attr_type_filter: for l in [r_must,r_may]: - for a in l.keys(): + for a in list(l.keys()): for afk,afv in attr_type_filter: try: schema_attr_type = self.sed[AttributeType][a] except KeyError: if raise_keyerror: - raise KeyError,'No attribute type found in sub schema by name %s' % (a) + raise KeyError('No attribute type found in sub schema by name %s' % (a)) # If there's no schema element for this attribute type # but still KeyError is to be ignored we filter it away del l[a] @@ -457,7 +456,9 @@ def urlfetch(uri,trace_level=0): if uri.startswith('ldap:') or uri.startswith('ldaps:') or uri.startswith('ldapi:'): import ldapurl ldap_url = ldapurl.LDAPUrl(uri) - l=ldap.initialize(ldap_url.initializeUrl(),trace_level) + + # This is an internal function; don't enable bytes_mode. + l=ldap.initialize(ldap_url.initializeUrl(),trace_level,bytes_mode=False) l.protocol_version = ldap.VERSION3 l.simple_bind_s(ldap_url.who or '', ldap_url.cred or '') subschemasubentry_dn = l.search_subschemasubentry_s(ldap_url.dn) @@ -474,8 +475,9 @@ def urlfetch(uri,trace_level=0): l.unbind_s() del l else: - import urllib,ldif - ldif_file = urllib.urlopen(uri) + import ldif + from ldap.compat import urlopen + ldif_file = urlopen(uri) ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1) ldif_parser.parse() subschemasubentry_dn,s_temp = ldif_parser.all_records[0] diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index ede7e216..20958c09 100644 --- a/Lib/ldap/schema/tokenizer.py +++ b/Lib/ldap/schema/tokenizer.py @@ -52,16 +52,15 @@ def extract_tokens(l,known_tokens): """ assert l[0].strip()=="(" and l[-1].strip()==")",ValueError(l) result = {} - result_has_key = result.has_key result.update(known_tokens) i = 0 l_len = len(l) while i +#else #include #endif +#endif static void free_attrs(char***, PyObject*); @@ -138,7 +142,7 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) if (list == Py_None) { /* None indicates a NULL mod_bvals */ - } else if (PyString_Check(list)) { + } else if (PyBytes_Check(list)) { /* Single string is a singleton list */ lm->mod_bvalues = PyMem_NEW(struct berval *, 2); if (lm->mod_bvalues == NULL) @@ -147,8 +151,8 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) if (lm->mod_bvalues[0] == NULL) goto nomem; lm->mod_bvalues[1] = NULL; - lm->mod_bvalues[0]->bv_len = PyString_Size(list); - lm->mod_bvalues[0]->bv_val = PyString_AsString(list); + lm->mod_bvalues[0]->bv_len = PyBytes_Size(list); + lm->mod_bvalues[0]->bv_val = PyBytes_AsString(list); } else if (PySequence_Check(list)) { nstrs = PySequence_Length(list); lm->mod_bvalues = PyMem_NEW(struct berval *, nstrs + 1); @@ -162,14 +166,14 @@ Tuple_to_LDAPMod( PyObject* tup, int no_op ) item = PySequence_GetItem(list, i); if (item == NULL) goto error; - if (!PyString_Check(item)) { + if (!PyBytes_Check(item)) { PyErr_SetObject( PyExc_TypeError, Py_BuildValue( "sO", - "expected a string in the list", item)); + "expected a byte string in the list", item)); Py_DECREF(item); goto error; } - lm->mod_bvalues[i]->bv_len = PyString_Size(item); - lm->mod_bvalues[i]->bv_val = PyString_AsString(item); + lm->mod_bvalues[i]->bv_len = PyBytes_Size(item); + lm->mod_bvalues[i]->bv_val = PyBytes_AsString(item); Py_DECREF(item); } if (nstrs == 0) @@ -264,7 +268,11 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { if (attrlist == Py_None) { /* None means a NULL attrlist */ - } else if (PyString_Check(attrlist)) { +#if PY_MAJOR_VERSION == 2 + } else if (PyBytes_Check(attrlist)) { +#else + } else if (PyUnicode_Check(attrlist)) { +#endif /* caught by John Benninghoff */ PyErr_SetObject( PyExc_TypeError, Py_BuildValue("sO", "expected *list* of strings, not a string", attrlist )); @@ -285,12 +293,21 @@ attrs_from_List( PyObject *attrlist, char***attrsp, PyObject** seq) { item = PySequence_Fast_GET_ITEM(*seq, i); if (item == NULL) goto error; - if (!PyString_Check(item)) { +#if PY_MAJOR_VERSION == 2 + /* Encoded by Python to UTF-8 */ + if (!PyBytes_Check(item)) { +#else + if (!PyUnicode_Check(item)) { +#endif PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", "expected string in list", item)); goto error; } - attrs[i] = PyString_AsString(item); +#if PY_MAJOR_VERSION == 2 + attrs[i] = PyBytes_AsString(item); +#else + attrs[i] = PyUnicode_AsUTF8(item); +#endif } attrs[len] = NULL; } @@ -547,7 +564,7 @@ static int interaction ( unsigned flags, if (result == NULL) /*searching for a better error code */ return LDAP_OPERATIONS_ERROR; - c_result = PyString_AsString(result); /*xxx Error checking?? */ + c_result = PyBytes_AsString(result); /*xxx Error checking?? */ /* according to the sasl docs, we should malloc() the returned string only for calls where interact->id == SASL_CB_PASS, so we @@ -643,7 +660,7 @@ l_ldap_sasl_bind_s( LDAPObject* self, PyObject* args ) if (ldaperror == LDAP_SASL_BIND_IN_PROGRESS) { if (servercred && servercred->bv_val && *servercred->bv_val) - return PyString_FromStringAndSize( servercred->bv_val, servercred->bv_len ); + return PyBytes_FromStringAndSize( servercred->bv_val, servercred->bv_len ); } else if (ldaperror != LDAP_SUCCESS) return LDAPerror( self->ldap, "l_ldap_sasl_bind_s" ); return PyInt_FromLong( ldaperror ); @@ -695,7 +712,7 @@ l_ldap_sasl_interactive_bind_s( LDAPObject* self, PyObject* args ) /* now we extract the sasl mechanism from the SASL Object */ mechanism = PyObject_GetAttrString(SASLObject, "mech"); if (mechanism == NULL) return NULL; - c_mechanism = PyString_AsString(mechanism); + c_mechanism = PyBytes_AsString(mechanism); Py_DECREF(mechanism); mechanism = NULL; @@ -1184,7 +1201,7 @@ l_ldap_whoami_s( LDAPObject* self, PyObject* args ) if ( ldaperror!=LDAP_SUCCESS ) return LDAPerror( self->ldap, "ldap_whoami_s" ); - result = LDAPberval_to_object(bvalue); + result = LDAPberval_to_unicode_object(bvalue); return result; } @@ -1380,45 +1397,43 @@ static PyMethodDef methods[] = { { NULL, NULL } }; -/* get attribute */ - -static PyObject* -getattr(LDAPObject* self, char* name) -{ - return Py_FindMethod(methods, (PyObject*)self, name); -} - -/* set attribute */ - -static int -setattr(LDAPObject* self, char* name, PyObject* value) -{ - PyErr_SetString(PyExc_AttributeError, name); - return -1; -} - /* type entry */ PyTypeObject LDAP_Type = { #if defined(MS_WINDOWS) || defined(__CYGWIN__) /* see http://www.python.org/doc/FAQ.html#3.24 */ - PyObject_HEAD_INIT(NULL) + PyVarObject_HEAD_INIT(NULL, 0) #else /* ! MS_WINDOWS */ - PyObject_HEAD_INIT(&PyType_Type) + PyVarObject_HEAD_INIT(&PyType_Type, 0) #endif /* MS_WINDOWS */ - 0, /*ob_size*/ "LDAP", /*tp_name*/ sizeof(LDAPObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)dealloc, /*tp_dealloc*/ 0, /*tp_print*/ - (getattrfunc)getattr, /*tp_getattr*/ - (setattrfunc)setattr, /*tp_setattr*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + 0, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index 4223735d..8cd6fc3e 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -7,8 +7,8 @@ #include "lber.h" #include "ldap.h" -#if LDAP_API_VERSION < 2000 -#error Current python-ldap requires OpenLDAP 2.x +#if LDAP_API_VERSION < 2040 +#error Current python-ldap requires OpenLDAP 2.4.x #endif #if PYTHON_API_VERSION < 1007 @@ -25,7 +25,7 @@ typedef struct { } LDAPObject; extern PyTypeObject LDAP_Type; -#define LDAPObject_Check(v) ((v)->ob_type == &LDAP_Type) +#define LDAPObject_Check(v) (Py_TYPE(v) == &LDAP_Type) extern LDAPObject *newLDAPObject( LDAP* ); diff --git a/Modules/berval.c b/Modules/berval.c index b1186695..73d7f9b0 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -89,7 +89,29 @@ LDAPberval_to_object(const struct berval *bv) Py_INCREF(ret); } else { - ret = PyString_FromStringAndSize(bv->bv_val, bv->bv_len); + ret = PyBytes_FromStringAndSize(bv->bv_val, bv->bv_len); + } + + return ret; +} + +/* + * Same as LDAPberval_to_object, but returns a Unicode PyObject. + * Use when the value is known to be text (for instance a distinguishedName). + * + * Returns a new Python object on success, or NULL on failure. + */ +PyObject * +LDAPberval_to_unicode_object(const struct berval *bv) +{ + PyObject *ret = NULL; + + if (!bv) { + ret = Py_None; + Py_INCREF(ret); + } + else { + ret = PyUnicode_FromStringAndSize(bv->bv_val, bv->bv_len); } return ret; diff --git a/Modules/berval.h b/Modules/berval.h index 514e9f93..2489e456 100644 --- a/Modules/berval.h +++ b/Modules/berval.h @@ -10,5 +10,6 @@ int LDAPberval_from_object(PyObject *obj, struct berval *bv); int LDAPberval_from_object_check(PyObject *obj); void LDAPberval_release(struct berval *bv); PyObject *LDAPberval_to_object(const struct berval *bv); +PyObject *LDAPberval_to_unicode_object(const struct berval *bv); #endif /* __h_berval_ */ diff --git a/Modules/common.h b/Modules/common.h index e29e20dd..0eea1d92 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -20,13 +20,6 @@ #include #endif -/* Backwards compability with Python prior 2.5 */ -#if PY_VERSION_HEX < 0x02050000 -typedef int Py_ssize_t; -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif - #include #define streq( a, b ) \ ( (*(a)==*(b)) && 0==strcmp(a,b) ) @@ -34,5 +27,11 @@ typedef int Py_ssize_t; void LDAPadd_methods( PyObject*d, PyMethodDef*methods ); #define PyNone_Check(o) ((o) == Py_None) +/* Py2/3 compatibility */ +#if PY_VERSION_HEX >= 0x03000000 +/* In Python 3, alias PyInt to PyLong */ +#define PyInt_FromLong PyLong_FromLong +#endif + #endif /* __h_common_ */ diff --git a/Modules/constants.c b/Modules/constants.c index 2c0ec1d3..7ed9e418 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -280,12 +280,6 @@ LDAPinit_constants( PyObject* d ) add_int(d,URL_ERR_BADSCOPE); add_int(d,URL_ERR_MEM); - /* author */ - - author = PyString_FromString("python-ldap Project"); - PyDict_SetItemString(d, "__author__", author); - Py_DECREF(author); - /* add_int(d,LIBLDAP_R); */ #ifdef HAVE_LIBLDAP_R obj = PyInt_FromLong(1); @@ -313,71 +307,71 @@ LDAPinit_constants( PyObject* d ) PyDict_SetItemString( d, "TLS_AVAIL", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_MANAGEDSAIT); + obj = PyUnicode_FromString(LDAP_CONTROL_MANAGEDSAIT); PyDict_SetItemString( d, "CONTROL_MANAGEDSAIT", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_PROXY_AUTHZ); + obj = PyUnicode_FromString(LDAP_CONTROL_PROXY_AUTHZ); PyDict_SetItemString( d, "CONTROL_PROXY_AUTHZ", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SUBENTRIES); + obj = PyUnicode_FromString(LDAP_CONTROL_SUBENTRIES); PyDict_SetItemString( d, "CONTROL_SUBENTRIES", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_VALUESRETURNFILTER); + obj = PyUnicode_FromString(LDAP_CONTROL_VALUESRETURNFILTER); PyDict_SetItemString( d, "CONTROL_VALUESRETURNFILTER", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_ASSERT); + obj = PyUnicode_FromString(LDAP_CONTROL_ASSERT); PyDict_SetItemString( d, "CONTROL_ASSERT", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_PRE_READ); + obj = PyUnicode_FromString(LDAP_CONTROL_PRE_READ); PyDict_SetItemString( d, "CONTROL_PRE_READ", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_POST_READ); + obj = PyUnicode_FromString(LDAP_CONTROL_POST_READ); PyDict_SetItemString( d, "CONTROL_POST_READ", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SORTREQUEST); + obj = PyUnicode_FromString(LDAP_CONTROL_SORTREQUEST); PyDict_SetItemString( d, "CONTROL_SORTREQUEST", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SORTRESPONSE); + obj = PyUnicode_FromString(LDAP_CONTROL_SORTRESPONSE); PyDict_SetItemString( d, "CONTROL_SORTRESPONSE", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_PAGEDRESULTS); + obj = PyUnicode_FromString(LDAP_CONTROL_PAGEDRESULTS); PyDict_SetItemString( d, "CONTROL_PAGEDRESULTS", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SYNC); + obj = PyUnicode_FromString(LDAP_CONTROL_SYNC); PyDict_SetItemString( d, "CONTROL_SYNC", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SYNC_STATE); + obj = PyUnicode_FromString(LDAP_CONTROL_SYNC_STATE); PyDict_SetItemString( d, "CONTROL_SYNC_STATE", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_SYNC_DONE); + obj = PyUnicode_FromString(LDAP_CONTROL_SYNC_DONE); PyDict_SetItemString( d, "CONTROL_SYNC_DONE", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_SYNC_INFO); + obj = PyUnicode_FromString(LDAP_SYNC_INFO); PyDict_SetItemString( d, "SYNC_INFO", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_PASSWORDPOLICYREQUEST); + obj = PyUnicode_FromString(LDAP_CONTROL_PASSWORDPOLICYREQUEST); PyDict_SetItemString( d, "CONTROL_PASSWORDPOLICYREQUEST", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_PASSWORDPOLICYRESPONSE); + obj = PyUnicode_FromString(LDAP_CONTROL_PASSWORDPOLICYRESPONSE); PyDict_SetItemString( d, "CONTROL_PASSWORDPOLICYRESPONSE", obj ); Py_DECREF(obj); - obj = PyString_FromString(LDAP_CONTROL_RELAX); + obj = PyUnicode_FromString(LDAP_CONTROL_RELAX); PyDict_SetItemString( d, "CONTROL_RELAX", obj ); Py_DECREF(obj); diff --git a/Modules/errors.c b/Modules/errors.c index 37319654..e5cb0ee8 100644 --- a/Modules/errors.c +++ b/Modules/errors.c @@ -80,7 +80,7 @@ LDAPerror( LDAP *l, char *msg ) if (info == NULL) return NULL; - str = PyString_FromString(ldap_err2string(errnum)); + str = PyUnicode_FromString(ldap_err2string(errnum)); if (str) PyDict_SetItemString( info, "desc", str ); Py_XDECREF(str); @@ -95,7 +95,7 @@ LDAPerror( LDAP *l, char *msg ) if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 && matched != NULL) { if (*matched != '\0') { - str = PyString_FromString(matched); + str = PyUnicode_FromString(matched); if (str) PyDict_SetItemString( info, "matched", str ); Py_XDECREF(str); @@ -104,13 +104,13 @@ LDAPerror( LDAP *l, char *msg ) } if (errnum == LDAP_REFERRAL) { - str = PyString_FromString(msg); + str = PyUnicode_FromString(msg); if (str) PyDict_SetItemString( info, "info", str ); Py_XDECREF(str); } else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { if (error != NULL && *error != '\0') { - str = PyString_FromString(error); + str = PyUnicode_FromString(error); if (str) PyDict_SetItemString( info, "info", str ); Py_XDECREF(str); diff --git a/Modules/functions.c b/Modules/functions.c index b2dbf83b..ffb6765c 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -76,9 +76,9 @@ l_ldap_str2dn( PyObject* unused, PyObject *args ) LDAPAVA *ava = rdn[j]; PyObject *tuple; - tuple = Py_BuildValue("(O&O&i)", - LDAPberval_to_object, &ava->la_attr, - LDAPberval_to_object, &ava->la_value, + tuple = Py_BuildValue("(O&O&i)", + LDAPberval_to_unicode_object, &ava->la_attr, + LDAPberval_to_unicode_object, &ava->la_value, ava->la_flags & ~(LDAP_AVA_FREE_ATTR|LDAP_AVA_FREE_VALUE)); if (!tuple) { Py_DECREF(rdnlist); diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 0bf86a1d..f3dc92a2 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -102,13 +102,13 @@ Tuple_to_LDAPControl( PyObject* tup ) berbytes.bv_len = 0; berbytes.bv_val = NULL; } - else if (PyString_Check(bytes)) { - berbytes.bv_len = PyString_Size(bytes); - berbytes.bv_val = PyString_AsString(bytes); + else if (PyBytes_Check(bytes)) { + berbytes.bv_len = PyBytes_Size(bytes); + berbytes.bv_val = PyBytes_AsString(bytes); } else { PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected a string", bytes)); + "expected bytes", bytes)); LDAPControl_DEL(lc); return NULL; } diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 7d8ed2f8..46c9c8a9 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -1,27 +1,44 @@ /* See https://www.python-ldap.org/ for details. */ #include "common.h" -#include "version.h" #include "constants.h" #include "errors.h" #include "functions.h" -#include "schema.h" #include "ldapcontrol.h" #include "LDAPObject.h" -DL_EXPORT(void) init_ldap(void); +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC PyInit__ldap(void); +#else +PyMODINIT_FUNC init_ldap(void); +#endif -/* dummy module methods */ +#define _STR(x) #x +#define STR(x) _STR(x) + +static char version_str[] = STR(LDAPMODULE_VERSION); +static char author_str[] = STR(LDAPMODULE_AUTHOR); +static char license_str[] = STR(LDAPMODULE_LICENSE); +static void +init_pkginfo( PyObject* m ) +{ + PyModule_AddStringConstant(m, "__version__", version_str); + PyModule_AddStringConstant(m, "__author__", author_str); + PyModule_AddStringConstant(m, "__license__", license_str); +} + +/* dummy module methods */ static PyMethodDef methods[] = { { NULL, NULL } }; /* module initialisation */ -DL_EXPORT(void) -init_ldap() + +/* Common initialization code */ +PyObject* init_ldap_module() { PyObject *m, *d; @@ -30,19 +47,45 @@ init_ldap() #endif /* Create the module and add the functions */ +#if PY_MAJOR_VERSION >= 3 + static struct PyModuleDef ldap_moduledef = { + PyModuleDef_HEAD_INIT, + "_ldap", /* m_name */ + "", /* m_doc */ + -1, /* m_size */ + methods, /* m_methods */ + }; + m = PyModule_Create(&ldap_moduledef); +#else m = Py_InitModule("_ldap", methods); +#endif + + PyType_Ready(&LDAP_Type); /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); - LDAPinit_version(d); + init_pkginfo(m); + LDAPinit_constants(d); LDAPinit_errors(d); LDAPinit_functions(d); - LDAPinit_schema(d); LDAPinit_control(d); /* Check for errors */ if (PyErr_Occurred()) Py_FatalError("can't initialize module _ldap"); + + return m; +} + + +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC init_ldap() { + init_ldap_module(); } +#else +PyMODINIT_FUNC PyInit__ldap() { + return init_ldap_module(); +} +#endif diff --git a/Modules/message.c b/Modules/message.c index 33127b68..1a289dbd 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -49,6 +49,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi BerElement *ber = NULL; PyObject* entrytuple; PyObject* attrdict; + PyObject* pydn; dn = ldap_get_dn( ld, entry ); if (dn == NULL) { @@ -91,22 +92,26 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi attr = ldap_next_attribute( ld, entry, ber ) ) { PyObject* valuelist; + PyObject* pyattr; + pyattr = PyUnicode_FromString(attr); + struct berval ** bvals = ldap_get_values_len( ld, entry, attr ); /* Find which list to append to */ - if ( PyMapping_HasKeyString( attrdict, attr ) ) { - valuelist = PyMapping_GetItemString( attrdict, attr ); + if ( PyDict_Contains( attrdict, pyattr ) ) { + valuelist = PyDict_GetItem( attrdict, pyattr ); } else { valuelist = PyList_New(0); - if (valuelist != NULL && PyMapping_SetItemString(attrdict, - attr, valuelist) == -1) { + if (valuelist != NULL && PyDict_SetItem(attrdict, + pyattr, valuelist) == -1) { Py_DECREF(valuelist); valuelist = NULL; /* catch error later */ } } if (valuelist == NULL) { + Py_DECREF(pyattr); Py_DECREF(attrdict); Py_DECREF(result); if (ber != NULL) @@ -125,6 +130,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi valuestr = LDAPberval_to_object(bvals[i]); if (PyList_Append( valuelist, valuestr ) == -1) { + Py_DECREF(pyattr); Py_DECREF(attrdict); Py_DECREF(result); Py_DECREF(valuestr); @@ -141,15 +147,25 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi } ldap_value_free_len(bvals); } + Py_DECREF(pyattr); Py_DECREF( valuelist ); ldap_memfree(attr); } + pydn = PyUnicode_FromString(dn); + if (pydn == NULL) { + Py_DECREF(result); + ldap_msgfree( m ); + ldap_memfree(dn); + return NULL; + } + if (add_ctrls) { - entrytuple = Py_BuildValue("(sOO)", dn, attrdict, pyctrls); + entrytuple = Py_BuildValue("(OOO)", pydn, attrdict, pyctrls); } else { - entrytuple = Py_BuildValue("(sO)", dn, attrdict); + entrytuple = Py_BuildValue("(OO)", pydn, attrdict); } + Py_DECREF(pydn); ldap_memfree(dn); Py_DECREF(attrdict); Py_XDECREF(pyctrls); @@ -191,7 +207,8 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi if (refs) { Py_ssize_t i; for (i=0; refs[i] != NULL; i++) { - PyObject *refstr = PyString_FromString(refs[i]); + /* A referal is a distinguishedName => unicode */ + PyObject *refstr = PyUnicode_FromString(refs[i]); PyList_Append(reflist, refstr); Py_DECREF(refstr); } @@ -218,6 +235,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi PyObject* valtuple; PyObject *valuestr; char *retoid = 0; + PyObject *pyoid; struct berval *retdata = 0; if (ldap_parse_intermediate( ld, entry, &retoid, &retdata, &serverctrls, 0 ) != LDAP_SUCCESS) { @@ -240,10 +258,17 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi valuestr = LDAPberval_to_object(retdata); ber_bvfree( retdata ); - valtuple = Py_BuildValue("(sOO)", retoid, + pyoid = PyUnicode_FromString(retoid); + ldap_memfree( retoid ); + if (pyoid == NULL) { + Py_DECREF(result); + ldap_msgfree( m ); + return NULL; + } + valtuple = Py_BuildValue("(OOO)", pyoid, valuestr ? valuestr : Py_None, pyctrls); - ldap_memfree( retoid ); + Py_DECREF(pyoid); Py_DECREF(valuestr); Py_XDECREF(pyctrls); PyList_Append(result, valtuple); diff --git a/Modules/message.h b/Modules/message.h index b4f60eb1..c3522ac3 100644 --- a/Modules/message.h +++ b/Modules/message.h @@ -10,4 +10,3 @@ extern PyObject* LDAPmessage_to_python( LDAP*ld, LDAPMessage*m, int add_ctrls, int add_intermediates ); #endif /* __h_message_ */ - diff --git a/Modules/options.c b/Modules/options.c index a437ca46..7cf996bf 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -204,7 +204,7 @@ LDAP_get_option(LDAPObject *self, int option) extensions = PyTuple_New(num_extensions); for (i = 0; i < num_extensions; i++) PyTuple_SET_ITEM(extensions, i, - PyString_FromString(apiinfo.ldapai_extensions[i])); + PyUnicode_FromString(apiinfo.ldapai_extensions[i])); /* return api info as a dictionary */ v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}", @@ -321,7 +321,7 @@ LDAP_get_option(LDAPObject *self, int option) Py_INCREF(Py_None); return Py_None; } - v = PyString_FromString(strval); + v = PyUnicode_FromString(strval); ldap_memfree(strval); return v; diff --git a/Modules/options.h b/Modules/options.h index dd613206..570fdc15 100644 --- a/Modules/options.h +++ b/Modules/options.h @@ -1,7 +1,7 @@ /* See https://www.python-ldap.org/ for details. */ -int LDAP_optionval_by_name(const char *name); -int LDAP_set_option(LDAPObject *self, int option, PyObject *value); +int LDAP_optionval_by_name(const char *name); +int LDAP_set_option(LDAPObject *self, int option, PyObject *value); PyObject *LDAP_get_option(LDAPObject *self, int option); void set_timeval_from_double( struct timeval *tv, double d ); diff --git a/Modules/schema.c b/Modules/schema.c deleted file mode 100644 index fb38b0aa..00000000 --- a/Modules/schema.c +++ /dev/null @@ -1,282 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#include "common.h" - -#include "schema.h" -#include "ldap_schema.h" - -/* - This utility function takes a null delimited C array of (null - delimited) C strings, creates its python equivalent and returns a - new reference to it. If the array is empty or the pointer to it is - NULL, an empty python array is returned. -*/ -PyObject* c_string_array_to_python(char **string_array) -{ - Py_ssize_t count = 0; - char **s; - PyObject *py_list; - if (string_array) { - for (s=string_array; *s != 0; s++) count++; - py_list = PyList_New(count); - count = 0; - for (s=string_array; *s != 0; s++){ - PyList_SetItem(py_list, count, PyString_FromString(*s)); - count++; - } - } else py_list=PyList_New(0); - return py_list; -} - - -/* - This function returns a list of tuples. The first entry of each - tuple is a string (lsei_name), and the second is a lists built from - lsei_values. - - Probably the C data structure is modeled along the lines of a - mapping "lsei_name -> (list of lsei_values)". However, there seems - to be no guarantee that a lsei_name is unique, so I dare not use a - python mapping for this beast... - */ -PyObject* schema_extension_to_python(LDAPSchemaExtensionItem **extensions) -{ - Py_ssize_t count = 0; - LDAPSchemaExtensionItem **e; - PyObject *py_list, *item_tuple; - if (extensions) { - for (e = extensions; *e !=0; e++) count++; - py_list = PyList_New(count); - count = 0; - for (e = extensions; *e !=0; e++) { - item_tuple = PyTuple_New(2); - PyTuple_SetItem(item_tuple, 0, - PyString_FromString((*e)->lsei_name)); - PyTuple_SetItem(item_tuple, 1, - c_string_array_to_python((*e)->lsei_values)); - PyList_SetItem(py_list, count, item_tuple); - count++; - } - } - else py_list=PyList_New(0); - return py_list; -} - - -/* - The following four functions do the boring job: they take a python - string, feed it into the respective parser functions provided by - openldap, and build a python list from the data structure returned - by the C function. - */ - -static char doc_ldap_str2objectclass[] = -""; - -static PyObject* -l_ldap_str2objectclass(PyObject* self, PyObject *args) -{ - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - char *oc_string; - const char *errp; - LDAPObjectClass *o; - PyObject *oc_names, *oc_sup_oids, *oc_at_oids_must, - *oc_at_oids_may, *py_ret; - - - if (!PyArg_ParseTuple(args, "si", &oc_string, &flag)) - return NULL; - o = ldap_str2objectclass( oc_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - - oc_sup_oids = c_string_array_to_python(o->oc_sup_oids); - oc_names = c_string_array_to_python(o->oc_names); - oc_at_oids_must = c_string_array_to_python(o->oc_at_oids_must); - oc_at_oids_may = c_string_array_to_python(o->oc_at_oids_may); - py_ret = PyList_New(9); - PyList_SetItem(py_ret, 0, PyString_FromString(o->oc_oid)); - PyList_SetItem(py_ret, 1, oc_names); - if (o->oc_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(o->oc_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(o->oc_obsolete)); - PyList_SetItem(py_ret, 4, oc_sup_oids); - PyList_SetItem(py_ret, 5, PyInt_FromLong(o->oc_kind)); - PyList_SetItem(py_ret, 6, oc_at_oids_must); - PyList_SetItem(py_ret, 7, oc_at_oids_may); - - PyList_SetItem(py_ret, 8, - schema_extension_to_python(o->oc_extensions)); - - ldap_objectclass_free(o); - return py_ret; -} - - -static char doc_ldap_str2attributetype[] = -""; - -static PyObject* -l_ldap_str2attributetype(PyObject* self, PyObject *args) -{ - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - char *at_string; - const char *errp; - LDAPAttributeType *a; - PyObject *py_ret; - PyObject *at_names; - - if (!PyArg_ParseTuple(args, "si", &at_string,&flag)) - return NULL; - a = ldap_str2attributetype( at_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - - py_ret = PyList_New(15); - PyList_SetItem(py_ret, 0, PyString_FromString(a->at_oid)); - at_names = c_string_array_to_python(a->at_names); - PyList_SetItem(py_ret, 1, at_names); - if (a->at_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(a->at_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(a->at_obsolete)); - if (a->at_sup_oid) { - PyList_SetItem(py_ret, 4, PyString_FromString(a->at_sup_oid)); - } else { - PyList_SetItem(py_ret, 4, PyString_FromString("")); - } - if (a->at_equality_oid) { - PyList_SetItem(py_ret, 5, PyString_FromString(a->at_equality_oid)); - } else { - PyList_SetItem(py_ret, 5, PyString_FromString("")); - } - if (a->at_ordering_oid) { - PyList_SetItem(py_ret, 6, PyString_FromString(a->at_ordering_oid)); - } else { - PyList_SetItem(py_ret, 6, PyString_FromString("")); - } - if (a->at_substr_oid) { - PyList_SetItem(py_ret, 7, PyString_FromString(a->at_substr_oid)); - } else { - PyList_SetItem(py_ret, 7, PyString_FromString("")); - } - if (a->at_syntax_oid) { - PyList_SetItem(py_ret, 8, PyString_FromString(a->at_syntax_oid)); - } else { - PyList_SetItem(py_ret, 8, PyString_FromString("")); - } - PyList_SetItem(py_ret, 9, PyInt_FromLong(a->at_syntax_len)); - PyList_SetItem(py_ret,10, PyInt_FromLong(a->at_single_value)); - PyList_SetItem(py_ret,11, PyInt_FromLong(a->at_collective)); - PyList_SetItem(py_ret,12, PyInt_FromLong(a->at_no_user_mod)); - PyList_SetItem(py_ret,13, PyInt_FromLong(a->at_usage)); - - PyList_SetItem(py_ret, 14, - schema_extension_to_python(a->at_extensions)); - ldap_attributetype_free(a); - return py_ret; -} - -static char doc_ldap_str2syntax[] = -""; - - -static PyObject* -l_ldap_str2syntax(PyObject* self, PyObject *args) -{ - LDAPSyntax *s; - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - const char *errp; - char *syn_string; - PyObject *py_ret, *syn_names; - - if (!PyArg_ParseTuple(args, "si", &syn_string,&flag)) - return NULL; - s = ldap_str2syntax(syn_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - py_ret = PyList_New(4); - PyList_SetItem(py_ret, 0, PyString_FromString(s->syn_oid)); - syn_names = c_string_array_to_python(s->syn_names); - PyList_SetItem(py_ret, 1, syn_names); - if (s->syn_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(s->syn_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, - schema_extension_to_python(s->syn_extensions)); - ldap_syntax_free(s); - return py_ret; -} - -static char doc_ldap_str2matchingrule[] = -""; - -static PyObject* -l_ldap_str2matchingrule(PyObject* self, PyObject *args) -{ - LDAPMatchingRule *m; - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - const char *errp; - char *mr_string; - PyObject *py_ret, *mr_names; - - if (!PyArg_ParseTuple(args, "si", &mr_string,&flag)) - return NULL; - m = ldap_str2matchingrule(mr_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - py_ret = PyList_New(6); - PyList_SetItem(py_ret, 0, PyString_FromString(m->mr_oid)); - mr_names = c_string_array_to_python(m->mr_names); - PyList_SetItem(py_ret, 1, mr_names); - if (m->mr_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(m->mr_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(m->mr_obsolete)); - if (m->mr_syntax_oid) { - PyList_SetItem(py_ret, 4, PyString_FromString(m->mr_syntax_oid)); - } else { - PyList_SetItem(py_ret, 4, PyString_FromString("")); - } - PyList_SetItem(py_ret, 5, - schema_extension_to_python(m->mr_extensions)); - ldap_matchingrule_free(m); - return py_ret; -} - -/* methods */ - -static PyMethodDef methods[] = { - { "str2objectclass", (PyCFunction)l_ldap_str2objectclass, METH_VARARGS, - doc_ldap_str2objectclass }, - { "str2attributetype", (PyCFunction)l_ldap_str2attributetype, - METH_VARARGS, doc_ldap_str2attributetype }, - { "str2syntax", (PyCFunction)l_ldap_str2syntax, - METH_VARARGS, doc_ldap_str2syntax }, - { "str2matchingrule", (PyCFunction)l_ldap_str2matchingrule, - METH_VARARGS, doc_ldap_str2matchingrule }, - { NULL, NULL } -}; - - -void -LDAPinit_schema( PyObject* d ) { - LDAPadd_methods( d, methods ); -} diff --git a/Modules/schema.h b/Modules/schema.h deleted file mode 100644 index eee00a3e..00000000 --- a/Modules/schema.h +++ /dev/null @@ -1,13 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_schema_ -#define __h_schema_ - - - -#include "common.h" -extern void LDAPinit_schema( PyObject* ); - - -#endif /* __h_schema_ */ - diff --git a/Modules/version.c b/Modules/version.c deleted file mode 100644 index b706b8ea..00000000 --- a/Modules/version.c +++ /dev/null @@ -1,19 +0,0 @@ -/* Set release version - * See https://www.python-ldap.org/ for details. */ - -#include "common.h" - -#define _STR(x) #x -#define STR(x) _STR(x) - -static char version_str[] = STR(LDAPMODULE_VERSION); - -void -LDAPinit_version( PyObject* d ) -{ - PyObject *version; - - version = PyString_FromString(version_str); - PyDict_SetItemString( d, "__version__", version ); - Py_DECREF(version); -} diff --git a/Modules/version.h b/Modules/version.h deleted file mode 100644 index 4d76b341..00000000 --- a/Modules/version.h +++ /dev/null @@ -1,11 +0,0 @@ -/* Set release version - * See https://www.python-ldap.org/ for details. */ - -#ifndef __h_version_ -#define __h_version_ - - -#include "common.h" -extern void LDAPinit_version( PyObject* d ); - -#endif /* __h_version_ */ diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index e69de29b..00000000 diff --git a/README b/README index d8e88bb3..431559dd 100644 --- a/README +++ b/README @@ -3,6 +3,7 @@ python-ldap: LDAP client API for Python --------------------------------------- What is python-ldap? +==================== python-ldap provides an object-oriented API to access LDAP directory servers from Python programs. Mainly it wraps the @@ -22,79 +23,106 @@ For module documentation, see: https://www.python-ldap.org/ Quick usage example: +==================== + +.. code-block:: python + import ldap l = ldap.initialize("ldap://my_ldap_server.my_domain") l.simple_bind_s("","") l.search_s("o=My Organisation, c=AU", ldap.SCOPE_SUBTREE, "objectclass=*") -See directory Demo/ of source distribution package for more +See directory ``Demo/`` of source distribution package for more example code. Author(s) contact and documentation: +==================================== https://www.python-ldap.org/ - If you are looking for help, please try the mailing list archives - first, then send a question to the mailing list. - Be warned that questions will be ignored if they can be - trivially answered by referring to the documentation. +If you are looking for help, please try the mailing list archives +first, then send a question to the mailing list. +Be warned that questions will be ignored if they can be +trivially answered by referring to the documentation. - If you are interested in helping, please contact the mailing list. - If you want new features or upgrades, please check the mailing list - archives and then enquire about any progress. +If you are interested in helping, please contact the mailing list. +If you want new features or upgrades, please check the mailing list +archives and then enquire about any progress. Acknowledgements: - - Thanks to Konstantin Chuguev - and Steffen Ries for working - on support for OpenLDAP 2.0.x features. - - Thanks to Michael Stroeder for the - modules ldif, ldapurl, ldap/schema/*.py, ldap/*.py and ldap/controls/*.py. - - Thanks to Hans Aschauer - for the C wrapper schema and SASL support. - - Thanks to Mauro Cicognini for the - WIN32/MSVC6 bits, and the pre-built WIN32 ldap.pyd. - - Thanks to Waldemar Osuch for contributing - the new-style docs based on reStructuredText. - - Thanks to Torsten Kurbad for the - easy_install support. - - Thanks to James Andrewartha for - significant contribution to Doc/*.tex. - - Thanks to Rich Megginson for extending - support for LDAPv3 controls and adding support for LDAPv3 extended - operations. - - Thanks to Peter Gietz, DAASI for funding some control modules. - - Thanks to Chris Mikkelson for various fixes and ldap.syncrepl. - - These very kind people have supplied patches or suggested changes: - - Federico Di Gregorio - John Benninghoff - Donn Cave - Jason Gunthorpe - gurney_j - Eric S. Johansson - David Margrave - Uche Ogbuji - Neale Pickett - Blake Weston - Wido Depping - Deepak Giridharagopal - Ingo Steuwer - Andreas Hasenack - Matej Vela - - Thanks to all the guys on the python-ldap mailing list for - their contributions and input into this package. +================= + +Thanks to Konstantin Chuguev +and Steffen Ries for working +on support for OpenLDAP 2.0.x features. + +Thanks to Michael Stroeder for the +modules ``ldif``, ``ldapurl``, ``ldap/schema/*.py``, ``ldap/*.py`` and ``ldap/controls/*.py``. + +Thanks to Hans Aschauer +for the C wrapper schema and SASL support. + +Thanks to Mauro Cicognini for the +WIN32/MSVC6 bits, and the pre-built WIN32 ``ldap.pyd``. + +Thanks to Waldemar Osuch for contributing +the new-style docs based on reStructuredText. + +Thanks to Torsten Kurbad for the +easy_install support. + +Thanks to James Andrewartha for +significant contribution to ``Doc/*.tex``. + +Thanks to Rich Megginson for extending +support for LDAPv3 controls and adding support for LDAPv3 extended +operations. + +Thanks to Peter Gietz, DAASI for funding some control modules. + +Thanks to Chris Mikkelson for various fixes and ldap.syncrepl. + +These very kind people have supplied patches or suggested changes: + +* Federico Di Gregorio +* John Benninghoff +* Donn Cave +* Jason Gunthorpe +* gurney_j +* Eric S. Johansson +* David Margrave +* Uche Ogbuji +* Neale Pickett +* Blake Weston +* Wido Depping +* Deepak Giridharagopal +* Ingo Steuwer +* Andreas Hasenack +* Matej Vela + +These people contributed to Python 3 porting (at https://github.com/pyldap/): +* A. Karl Kornel +* Alex Willmer +* Aymeric Augustin +* Bradley Baetz +* Christian Heimes +* Dirk Mueller +* Jon Dufresne +* Martin Basti +* Miro Hroncok +* Paul Aurich +* Petr Viktorin +* Pieterjan De Potter +* Raphaël Barrois +* Robert Kuska +* Stanislav Láznička +* Tobias Bräutigam +* Tom van Dijk +* Wentao Han +* William Brown + +Thanks to all the guys on the python-ldap mailing list for +their contributions and input into this package. Thanks! We may have missed someone: please mail us if we have omitted your name. diff --git a/README.rst b/README.rst new file mode 120000 index 00000000..100b9382 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +README \ No newline at end of file diff --git a/TODO b/TODO index 456cfeaf..b728424a 100644 --- a/TODO +++ b/TODO @@ -20,7 +20,6 @@ Lib/ (Everybody asking for the latter should check the mailing list archive first.) - Caching of search requests for each LDAPObject instance - LDIF parser for replication logs and change records -- DSMLv2 support Tests/ - Clean up and finish the mess of small test scripts started. diff --git a/Tests/__init__.py b/Tests/__init__.py index b7b53783..46b38618 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -5,14 +5,18 @@ See https://www.python-ldap.org/ for details. """ -import t_cext -import t_cidict -import t_ldap_dn -import t_ldap_filter -import t_ldap_functions -import t_ldap_modlist -import t_ldap_schema_tokenizer -import t_ldapurl -import t_ldif -import t_ldapobject -import t_ldap_schema_subentry +from __future__ import absolute_import + +from . import t_bind +from . import t_cext +from . import t_cidict +from . import t_ldap_dn +from . import t_ldap_filter +from . import t_ldap_functions +from . import t_ldap_modlist +from . import t_ldap_schema_tokenizer +from . import t_ldapurl +from . import t_ldif +from . import t_ldapobject +from . import t_edit +from . import t_ldap_schema_subentry diff --git a/Tests/t_bind.py b/Tests/t_bind.py new file mode 100644 index 00000000..373688b7 --- /dev/null +++ b/Tests/t_bind.py @@ -0,0 +1,82 @@ +from __future__ import unicode_literals + +import sys + +if sys.version_info[0] <= 2: + PY2 = True + text_type = unicode +else: + PY2 = False + text_type = str + +import ldap, unittest +from slapdtest import SlapdObject +from ldap.ldapobject import LDAPObject + +server = None + + +class TestBinds(unittest.TestCase): + + def setUp(self): + global server + if server is None: + server = SlapdObject() + server.start() + + self.server = server + self.unicode_val = "abc\U0001f498def" + self.unicode_val_bytes = self.unicode_val.encode('utf-8') + + self.dn_unicode = "CN=" + self.unicode_val + self.dn_bytes = self.dn_unicode.encode('utf-8') + + def _get_ldapobject(self, bytes_mode=None): + l = LDAPObject(self.server.ldap_uri, bytes_mode=bytes_mode) + l.protocol_version = 3 + l.set_option(ldap.OPT_REFERRALS,0) + return l + + def test_simple_bind(self): + l = self._get_ldapobject(False) + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_unicode, self.unicode_val) + + def test_unicode_bind(self): + l = self._get_ldapobject(False) + l.simple_bind(self.dn_unicode, "ascii") + + l = self._get_ldapobject(False) + l.simple_bind("CN=user", self.unicode_val) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_unicode_bind_bytesmode(self): + l = self._get_ldapobject(True) + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_unicode, self.unicode_val_bytes) + + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_bytes, self.unicode_val) + + # Works when encoded to UTF-8 + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_bytes, self.unicode_val_bytes) + + def test_unicode_bind_no_bytesmode(self): + l = self._get_ldapobject(False) + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_bytes, self.unicode_val) + + # Works fine in Python 3 because 'cred' (the password) is read in + # using the "s#" format which, unlike "s", accepts either a str + # (unicode) *or* bytes. + # + # with self.assertRaises(TypeError): + # l.simple_bind_s(self.dn_unicode, self.unicode_val_bytes) + + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_unicode, self.unicode_val) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 6add784c..d4740d0c 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + import os import unittest from slapdtest import SlapdTestCase @@ -26,7 +28,7 @@ class TestLdapCExtension(SlapdTestCase): @classmethod def setUpClass(cls): - SlapdTestCase.setUpClass() + super(TestLdapCExtension, cls).setUpClass() # add two initial objects after server was started and is still empty suffix_dc = cls.server.suffix.split(',')[0][3:] cls.server._log.debug( @@ -64,10 +66,10 @@ def _open_conn(self, bind=True): return l def assertNotNone(self, expr, msg=None): - self.failIf(expr is None, msg or repr(expr)) + self.assertFalse(expr is None, msg or repr(expr)) def assertNone(self, expr, msg=None): - self.failIf(expr is not None, msg or repr(expr)) + self.assertFalse(expr is not None, msg or repr(expr)) # Test for the existence of a whole bunch of constants # that the C module is supposed to export @@ -192,10 +194,10 @@ def test_anon_rootdse_search(self): l = self._open_conn(bind=False) # see if we can get the rootdse with anon search (without prior bind) m = l.search_ext( - "", + '', _ldap.SCOPE_BASE, '(objectClass=*)', - ['objectClass', 'namingContexts'], + [str('objectClass'), str('namingContexts')], ) self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) @@ -205,9 +207,9 @@ def test_anon_rootdse_search(self): self.assertEqual(ctrls, []) root_dse = pmsg[0][1] self.assertTrue('objectClass' in root_dse) - self.assertTrue('OpenLDAProotDSE' in root_dse['objectClass']) + self.assertTrue(b'OpenLDAProotDSE' in root_dse['objectClass']) self.assertTrue('namingContexts' in root_dse) - self.assertEqual(root_dse['namingContexts'], [self.server.suffix]) + self.assertEqual(root_dse['namingContexts'], [self.server.suffix.encode('ascii')]) def test_unbind(self): l = self._open_conn() @@ -235,8 +237,8 @@ def test_search_ext_individual(self): self.assertEqual(len(pmsg[0]), 2) self.assertEqual(pmsg[0][0], self.server.suffix) self.assertEqual(pmsg[0][0], self.server.suffix) - self.assertTrue('dcObject' in pmsg[0][1]['objectClass']) - self.assertTrue('organization' in pmsg[0][1]['objectClass']) + self.assertTrue(b'dcObject' in pmsg[0][1]['objectClass']) + self.assertTrue(b'organization' in pmsg[0][1]['objectClass']) self.assertEqual(msgid, m) self.assertEqual(ctrls, []) @@ -253,7 +255,7 @@ def test_abandon(self): self.assertNone(ret) try: r = l.result4(m, _ldap.MSG_ALL, 0.3) # (timeout /could/ be longer) - except _ldap.TIMEOUT, e: + except _ldap.TIMEOUT as e: pass else: self.fail("expected TIMEOUT, got %r" % r) @@ -278,9 +280,9 @@ def test_add(self): m = l.add_ext( "cn=Foo," + self.server.suffix, [ - ('objectClass', 'organizationalRole'), - ('cn', 'Foo'), - ('description', 'testing'), + ('objectClass', b'organizationalRole'), + ('cn', b'Foo'), + ('description', b'testing'), ] ) self.assertEqual(type(m), type(0)) @@ -302,9 +304,9 @@ def test_add(self): ( 'cn=Foo,'+self.server.suffix, { - 'objectClass': ['organizationalRole'], - 'cn': ['Foo'], - 'description': ['testing'], + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo'], + 'description': [b'testing'], } ) ) @@ -319,10 +321,10 @@ def test_compare(self): m = l.add_ext( dn, [ - ('objectClass', 'person'), - ('sn', 'CompareTest'), - ('cn', 'CompareTest'), - ('userPassword', 'the_password'), + ('objectClass', b'person'), + ('sn', b'CompareTest'), + ('cn', b'CompareTest'), + ('userPassword', b'the_password'), ], ) self.assertEqual(type(m), type(0)) @@ -373,8 +375,8 @@ def test_delete(self): m = l.add_ext( dn, [ - ('objectClass', 'organizationalRole'), - ('cn', 'Deleteme'), + ('objectClass', b'organizationalRole'), + ('cn', b'Deleteme'), ] ) self.assertEqual(type(m), type(0)) @@ -395,7 +397,7 @@ def test_modify_no_such_object(self): m = l.modify_ext( "cn=DoesNotExist,"+self.server.suffix, [ - (_ldap.MOD_ADD, 'description', ['blah']), + (_ldap.MOD_ADD, 'description', [b'blah']), ] ) try: @@ -413,7 +415,7 @@ def test_modify_no_such_object_empty_attrs(self): m = l.modify_ext( "cn=DoesNotExist,"+self.server.suffix, [ - (_ldap.MOD_ADD, 'description', ['dummy']), + (_ldap.MOD_ADD, 'description', [b'dummy']), ] ) self.assertTrue(isinstance(m, int)) @@ -434,10 +436,10 @@ def test_modify(self): m = l.add_ext( dn, [ - ('objectClass', 'person'), - ('cn', 'AddToMe'), - ('sn', 'Modify'), - ('description', 'a description'), + ('objectClass', b'person'), + ('cn', b'AddToMe'), + ('sn', b'Modify'), + ('description', b'a description'), ] ) self.assertEqual(type(m), type(0)) @@ -447,7 +449,7 @@ def test_modify(self): m = l.modify_ext( dn, [ - (_ldap.MOD_ADD, 'description', ['b desc', 'c desc']), + (_ldap.MOD_ADD, 'description', [b'b desc', b'c desc']), ] ) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) @@ -466,7 +468,7 @@ def test_modify(self): self.assertEqual(pmsg[0][0], dn) d = list(pmsg[0][1]['description']) d.sort() - self.assertEqual(d, ['a description', 'b desc', 'c desc']) + self.assertEqual(d, [b'a description', b'b desc', b'c desc']) def test_rename(self): l = self._open_conn() @@ -474,8 +476,8 @@ def test_rename(self): m = l.add_ext( dn, [ - ('objectClass', 'organizationalRole'), - ('cn', 'RenameMe'), + ('objectClass', b'organizationalRole'), + ('cn', b'RenameMe'), ] ) self.assertEqual(type(m), type(0)) @@ -507,15 +509,15 @@ def test_rename(self): self.assertEqual(ctrls, []) self.assertEqual(len(pmsg), 1) self.assertEqual(pmsg[0][0], dn2) - self.assertEqual(pmsg[0][1]['cn'], ['IAmRenamed']) + self.assertEqual(pmsg[0][1]['cn'], [b'IAmRenamed']) # create the container containerDn = "ou=RenameContainer,"+self.server.suffix m = l.add_ext( containerDn, [ - ('objectClass', 'organizationalUnit'), - ('ou', 'RenameContainer'), + ('objectClass', b'organizationalUnit'), + ('ou', b'RenameContainer'), ] ) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) @@ -551,7 +553,7 @@ def test_rename(self): self.assertEqual(ctrls, []) self.assertEqual(len(pmsg), 1) self.assertEqual(pmsg[0][0], dn3) - self.assertEqual(pmsg[0][1]['cn'], ['IAmRenamedAgain']) + self.assertEqual(pmsg[0][1]['cn'], [b'IAmRenamedAgain']) def test_whoami(self): @@ -576,6 +578,13 @@ def test_whoami_anonymous(self): r = l.whoami_s() self.assertEqual("", r) + def test_whoami_after_unbind(self): + # https://github.com/pyldap/pyldap/issues/29 + l = self._open_conn(bind=True) + l.unbind_ext() + with self.assertRaises(_ldap.LDAPError): + l.whoami_s() + def test_passwd(self): l = self._open_conn() # first, create a user to change password on @@ -583,10 +592,10 @@ def test_passwd(self): m = l.add_ext( dn, [ - ('objectClass', 'person'), - ('sn', 'PasswordTest'), - ('cn', 'PasswordTest'), - ('userPassword', 'initial'), + ('objectClass', b'person'), + ('sn', b'PasswordTest'), + ('cn', b'PasswordTest'), + ('userPassword', b'initial'), ] ) self.assertEqual(type(m), type(0)) @@ -683,7 +692,7 @@ def test_errno107(self): try: m = l.simple_bind("", "") r = l.result4(m, _ldap.MSG_ALL, self.timeout) - except _ldap.SERVER_DOWN, ldap_err: + except _ldap.SERVER_DOWN as ldap_err: errno = ldap_err.args[0]['errno'] if errno != 107: self.fail("expected errno=107, got %d" % errno) diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 3f9e8e43..00d07266 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -32,15 +32,17 @@ def test_cidict(self): cix["xYZ"] = 987 self.assertEqual(cix["XyZ"], 987) self.assertEqual(cix.get("xyz", None), 987) - cix_keys = cix.keys() - cix_keys.sort() + cix_keys = sorted(cix.keys()) self.assertEqual(cix_keys, ['AbCDeF','xYZ']) - cix_items = cix.items() - cix_items.sort() + cix_items = sorted(cix.items()) self.assertEqual(cix_items, [('AbCDeF',123), ('xYZ',987)]) del cix["abcdEF"] - self.assertEqual(cix._keys.has_key("abcdef"), False) - self.assertEqual(cix._keys.has_key("AbCDef"), False) + self.assertEqual("abcdef" in cix._keys, False) + self.assertEqual("AbCDef" in cix._keys, False) + self.assertEqual("abcdef" in cix, False) + self.assertEqual("AbCDef" in cix, False) + self.assertEqual(cix.has_key("abcdef"), False) + self.assertEqual(cix.has_key("AbCDef"), False) if __name__ == '__main__': diff --git a/Tests/t_edit.py b/Tests/t_edit.py new file mode 100644 index 00000000..9aee43e9 --- /dev/null +++ b/Tests/t_edit.py @@ -0,0 +1,89 @@ +from __future__ import unicode_literals + +import sys + +if sys.version_info[0] <= 2: + PY2 = True + text_type = unicode +else: + PY2 = False + text_type = str + +import ldap, unittest +from slapdtest import SlapdObject + +from ldap.ldapobject import LDAPObject + +server = None + + +class EditionTests(unittest.TestCase): + + def setUp(self): + global server + if server is None: + server = SlapdObject() + server.start() + base = server.suffix + suffix_dc = base.split(',')[0][3:] + + # insert some Foo* objects via ldapadd + server.ldapadd("\n".join([ + 'dn: '+server.suffix, + 'objectClass: dcObject', + 'objectClass: organization', + 'dc: '+suffix_dc, + 'o: '+suffix_dc, + '', + 'dn: '+server.root_dn, + 'objectClass: applicationProcess', + 'cn: '+server.root_cn, + '', + "dn: cn=Foo1,"+base, + "objectClass: organizationalRole", + "cn: Foo1", + "", + "dn: cn=Foo2,"+base, + "objectClass: organizationalRole", + "cn: Foo2", + "", + "dn: cn=Foo3,"+base, + "objectClass: organizationalRole", + "cn: Foo3", + "", + "dn: ou=Container,"+base, + "objectClass: organizationalUnit", + "ou: Container", + "", + "dn: cn=Foo4,ou=Container,"+base, + "objectClass: organizationalRole", + "cn: Foo4", + "", + ])+"\n") + + l = LDAPObject(server.ldap_uri, bytes_mode=False) + l.protocol_version = 3 + l.set_option(ldap.OPT_REFERRALS,0) + l.simple_bind_s(server.root_dn, + server.root_pw) + self.ldap = l + self.server = server + + def test_add_object(self): + base = self.server.suffix + dn = "cn=Added,ou=Container," + base + self.ldap.add_ext_s(dn, [ + ("objectClass", [b'organizationalRole']), + ("cn", [b'Added']), + ]) + + # Lookup the object + result = self.ldap.search_s(base, ldap.SCOPE_SUBTREE, '(cn=Added)', ['*']) + self.assertEqual(result, [ + ("cn=Added,ou=Container," + base, + {'cn': [b'Added'], 'objectClass': [b'organizationalRole']}), + ]) + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 043cc702..459c1dcf 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + # from Python's standard lib import unittest @@ -27,12 +29,219 @@ def test_is_dn(self): self.assertEqual(ldap.dn.is_dn(',cn=foobar,ou=ae-dir'), False) self.assertEqual(ldap.dn.is_dn('cn=foobar,ou=ae-dir,'), False) self.assertEqual(ldap.dn.is_dn('uid=xkcd,cn=foobar,ou=ae-dir'), True) + self.assertEqual(ldap.dn.is_dn('cn=äöüÄÖÜß,o=äöüÄÖÜß'), True) self.assertEqual( ldap.dn.is_dn( - 'cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c.o=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f' + r'cn=\c3\a4\c3\b6\c3\bc\c3\84\c3\96\c3\9c\c3\9f,o=\c3\a4\c3\b6\c3\bc\c3\84\c3\96\c3\9c\c3\9f' ), True ) + def test_escape_dn_chars(self): + """ + test function escape_dn_chars() + """ + self.assertEqual(ldap.dn.escape_dn_chars('foobar'), 'foobar') + self.assertEqual(ldap.dn.escape_dn_chars('foo,bar'), 'foo\\,bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo=bar'), 'foo\\=bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo#bar'), 'foo#bar') + self.assertEqual(ldap.dn.escape_dn_chars('#foobar'), '\\#foobar') + self.assertEqual(ldap.dn.escape_dn_chars('foo bar'), 'foo bar') + self.assertEqual(ldap.dn.escape_dn_chars(' foobar'), '\\ foobar') + + def test_str2dn(self): + """ + test function str2dn() + """ + self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual( + ldap.dn.str2dn('uid=test42,ou=Testing,dc=example,dc=com'), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test42+uidNumber=42,ou=Testing,dc=example,dc=com'), + [ + [('uid', 'test42', 1), ('uidNumber', '42', 1) ], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test42,ou=Testing,dc=example,dc=com', flags=0), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test42; ou=Testing; dc=example; dc=com', flags=ldap.DN_FORMAT_LDAPV2), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test\\, 42,ou=Testing,dc=example,dc=com', flags=0), + [ + [('uid', 'test, 42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('cn=äöüÄÖÜß,dc=example,dc=com', flags=0), + [ + [('cn', 'äöüÄÖÜß', 4)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('cn=\\c3\\a4\\c3\\b6\\c3\\bc\\c3\\84\\c3\\96\\c3\\9c\\c3\\9f,dc=example,dc=com', flags=0), + [ + [('cn', 'äöüÄÖÜß', 4)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + + def test_dn2str(self): + """ + test function dn2str() + """ + self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42,ou=Testing,dc=example,dc=com', + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('uidNumber', '42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42+uidNumber=42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test, 42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test\\, 42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', 'äöüÄÖÜß', 4)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'cn=äöüÄÖÜß,dc=example,dc=com' + ) + + def test_explode_dn(self): + """ + test function explode_dn() + """ + self.assertEqual(ldap.dn.explode_dn(''), []) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com'), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', flags=0), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42; ou=Testing; dc=example; dc=com', flags=ldap.DN_FORMAT_LDAPV2), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', notypes=True), + ['test42', 'Testing', 'example', 'com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test\\, 42,ou=Testing,dc=example,dc=com', flags=0), + ['uid=test\\, 42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('cn=äöüÄÖÜß,dc=example,dc=com', flags=0), + ['cn=äöüÄÖÜß', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('cn=\\c3\\a4\\c3\\b6\\c3\\bc\\c3\\84\\c3\\96\\c3\\9c\\c3\\9f,dc=example,dc=com', flags=0), + ['cn=äöüÄÖÜß', 'dc=example', 'dc=com'] + ) + + def test_explode_rdn(self): + """ + test function explode_rdn() + """ + self.assertEqual(ldap.dn.explode_rdn(''), []) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42'), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=False, flags=0), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=0, flags=0), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42+uidNumber=42', flags=0), + ['uid=test42', 'uidNumber=42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=True), + ['test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=1), + ['test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test\\+ 42', flags=0), + ['uid=test\\+ 42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('cn=äöüÄÖÜß', flags=0), + ['cn=äöüÄÖÜß'] + ) + self.assertEqual( + ldap.dn.explode_rdn('cn=\\c3\\a4\\c3\\b6\\c3\\bc\\c3\\84\\c3\\96\\c3\\9c\\c3\\9f', flags=0), + ['cn=äöüÄÖÜß'] + ) + + if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index de6421a4..83ed0061 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -10,6 +10,7 @@ import ldif import ldap.schema +from ldap.schema.models import ObjectClass TEST_SUBSCHEMA_FILES = ( 'Tests/ldif/subschema-ipa.demo1.freeipa.org.ldif', @@ -30,6 +31,15 @@ def test_subschema_file(self): _, subschema_subentry = ldif_parser.all_records[0] sub_schema = ldap.schema.SubSchema(subschema_subentry) + # Smoke-check for listall() and attribute_types() + for objclass in sub_schema.listall(ObjectClass): + must, may = sub_schema.attribute_types([objclass]) + + for oid, attributetype in must.items(): + self.assertEqual(attributetype.oid, oid) + for oid, attributetype in may.items(): + self.assertEqual(attributetype.oid, oid) + if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py new file mode 100644 index 00000000..831c0631 --- /dev/null +++ b/Tests/t_ldap_syncrepl.py @@ -0,0 +1,467 @@ +# -*- coding: utf-8 -*- +""" +Automatic tests for python-ldap's module ldap.syncrepl + +See http://www.python-ldap.org/ for details. +""" + + +import os +import unittest +import shelve + +from slapdtest import SlapdObject, SlapdTestCase + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap +from ldap.ldapobject import SimpleLDAPObject +from ldap.syncrepl import SyncreplConsumer + +# a template string for generating simple slapd.conf file +SLAPD_CONF_PROVIDER_TEMPLATE = r""" +serverID %(serverid)s +moduleload back_%(database)s +moduleload syncprov +include "%(schema_prefix)s/core.schema" +loglevel %(loglevel)s +allow bind_v2 + +authz-regexp + "gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" + "%(rootdn)s" + +database %(database)s +directory "%(directory)s" +suffix "%(suffix)s" +rootdn "%(rootdn)s" +rootpw "%(rootpw)s" +overlay syncprov +syncprov-checkpoint 100 10 +syncprov-sessionlog 100 +index objectclass,entryCSN,entryUUID eq +""" + +# Define initial data load, both as an LDIF and as a dictionary. +LDIF_TEMPLATE = """dn: %(suffix)s +objectClass: dcObject +objectClass: organization +dc: %(dc)s +o: %(dc)s + +dn: %(rootdn)s +objectClass: applicationProcess +objectClass: simpleSecurityObject +cn: %(rootcn)s +userPassword: %(rootpw)s + +dn: cn=Foo1,%(suffix)s +objectClass: organizationalRole +cn: Foo1 + +dn: cn=Foo2,%(suffix)s +objectClass: organizationalRole +cn: Foo2 + +dn: cn=Foo3,%(suffix)s +objectClass: organizationalRole +cn: Foo3 + +dn: ou=Container,%(suffix)s +objectClass: organizationalUnit +ou: Container + +dn: cn=Foo4,ou=Container,%(suffix)s +objectClass: organizationalRole +cn: Foo4 + +""" + +# NOTE: For the dict, it needs to be kept up-to-date as we make changes! +LDAP_ENTRIES = { + 'ou=Container,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'organizationalUnit'], + 'ou': [b'Container'] + }, + 'cn=Foo2,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo2'] + }, + 'cn=Foo4,ou=Container,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo4'] + }, + 'cn=Manager,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'applicationProcess', b'simpleSecurityObject'], + 'userPassword': [b'password'], + 'cn': [b'Manager'] + }, + 'cn=Foo3,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo3'] + }, + 'cn=Foo1,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo1'] + }, + 'dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': [b'dcObject', b'organization'], + 'dc': [b'slapd-test'], + 'o': [b'slapd-test'] + } +} + + +class SyncreplProvider(SlapdObject): + slapd_conf_template = SLAPD_CONF_PROVIDER_TEMPLATE + + +class SyncreplClient(SimpleLDAPObject, SyncreplConsumer): + """ + This is a very simple class to start up the syncrepl search + and handle callbacks that come in. + + Needs to be separate, because once an LDAP client starts a syncrepl + search, it can't be used for anything else. + """ + server_class = SyncreplProvider + + def __init__(self, uri, dn, password, storage=None): + """ + Set up our object by creating a search client, connecting, and binding. + """ + + if storage is not None: + self.data = shelve.open(storage) + self.uuid_dn = shelve.open(storage + 'uuid_dn') + self.dn_attrs = shelve.open(storage + 'dn_attrs') + self.using_shelve = True + else: + self.data = {} + self.uuid_dn = {} + self.dn_attrs = {} + self.using_shelve = False + + self.data['cookie'] = None + self.present = [] + self.refresh_done = False + + SimpleLDAPObject.__init__(self, uri) + self.simple_bind_s(dn, password) + + + def unbind_s(self): + """ + In addition to unbinding from LDAP, we need to close the shelf. + """ + if self.using_shelve is True: + self.data.close() + self.uuid_dn.close() + self.dn_attrs.close() + SimpleLDAPObject.unbind_s(self) + + + def search(self, search_base, search_mode): + """ + Start a syncrepl search operation, given a base DN and search mode. + """ + self.search_id = self.syncrepl_search( + search_base, + ldap.SCOPE_SUBTREE, + mode=search_mode, + filterstr='(objectClass=*)' + ) + + + def cancel(self): + """ + A simple wrapper to call parent class with syncrepl search ID. + """ + SimpleLDAPObject.cancel(self, self.search_id) + + + def poll(self, timeout=None, all=0): + """ + Take the params, add the syncrepl search ID, and call the proper poll. + """ + return self.syncrepl_poll( + self.search_id, + timeout=timeout, + all=all + ) + + + def syncrepl_get_cookie(self): + """ + Pull cookie from storage, if one exists. + """ + return self.data['cookie'] + + + def syncrepl_set_cookie(self, cookie): + """ + Update stored cookie. + """ + self.data['cookie'] = cookie + + + def syncrepl_refreshdone(self): + """ + Just update a variable. + """ + self.refresh_done = True + + + def syncrepl_delete(self, uuids): + """ + Delete the given items from both maps. + """ + for uuid in uuids: + del self.dn_attrs[self.uuid_dn[uuid]] + del self.uuid_dn[uuid] + + + def syncrepl_entry(self, dn, attrs, uuid): + """ + Handles adds and changes (including DN changes). + """ + if uuid in self.uuid_dn: + # Catch changing DNs. + if dn != self.uuid_dn[uuid]: + # Delete data associated with old DN. + del self.dn_attrs[self.uuid_dn[uuid]] + + # Update both maps. + self.uuid_dn[uuid] = dn + self.dn_attrs[dn] = attrs + + + def syncrepl_present(self, uuids, refreshDeletes=False): + """ + The 'present' message from the LDAP server is the most complicated + part of the refresh phase. Suggest looking here for more info: + http://syncrepl-client.readthedocs.io/en/latest/client.html + """ + if (uuids is not None) and (refreshDeletes is False): + self.present.extend(uuids) + + elif (uuids is None) and (refreshDeletes is False): + deleted_uuids = list() + for uuid in self.uuid_dn.keys(): + if uuid not in self.present: + deleted_uuids.append(uuid) + + if len(deleted_uuids) > 0: + self.syncrepl_delete(deleted_uuids) + + elif (uuids is not None) and (refreshDeletes is True): + self.syncrepl_delete(uuids) + + elif (uuids is None) and (refreshDeletes is True): + pass + + +class Test00_Syncrepl(SlapdTestCase): + """ + This is a test of all the basic Syncrepl operations. It covers starting a + search (both types of search), doing the refresh part of the search, + and checking that we got everything that we expected. We also test that + timeouts and cancellation are working properly. + """ + + server_class = SyncreplProvider + ldap_object_class = SimpleLDAPObject + + @classmethod + def setUpClass(cls): + super(Test00_Syncrepl, cls).setUpClass() + # insert some Foo* objects via ldapadd + cls.server.ldapadd( + LDIF_TEMPLATE % { + 'suffix':cls.server.suffix, + 'rootdn':cls.server.root_dn, + 'rootcn':cls.server.root_cn, + 'rootpw':cls.server.root_pw, + 'dc': cls.server.suffix.split(',')[0][3:], + } + ) + + + def setUp(self): + try: + self._ldap_conn + except AttributeError: + # open local LDAP connection + self._ldap_conn = self._open_ldap_conn() + + + def tearDown(self): + self.tester.unbind_s() + + + def test_refreshOnly_search(self): + ''' + Test to see if we can initialize a syncrepl search. + ''' + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshOnly' + ) + + + def test_refreshAndPersist_search(self): + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + + def test_refreshOnly_poll_full(self): + """ + Test doing a full refresh cycle, and check what we got. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshOnly' + ) + poll_result = self.tester.poll( + all=1, + timeout=None + ) + self.assertFalse(poll_result) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + + def test_refreshAndPersist_poll_only(self): + """ + Test the refresh part of refresh-and-persist, and check what we got. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Make sure to stop the test before going into persist mode. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + + def test_refreshAndPersist_timeout(self): + """ + Make sure refreshAndPersist can handle a search with timeouts. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Run a quick refresh, that shouldn't have any changes. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + # Again, server data should not have changed. + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + # Run a search with timeout. + # Nothing is changing the server, so it shoud timeout. + self.assertRaises( + ldap.TIMEOUT, + self.tester.poll, + all=0, + timeout=1 + ) + + + def test_refreshAndPersist_cancelled(self): + """ + Make sure refreshAndPersist can handle cancelling a syncrepl search. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Run a quick refresh, that shouldn't have any changes. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + # Again, server data should not have changed. + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + # Request cancellation. + self.tester.cancel() + + # Run another poll, without timeout, but which should cancel out. + self.assertRaises( + ldap.CANCELLED, + self.tester.poll, + all=1, + timeout=None + ) + + # Server data should still be intact. + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) + + + # TODO: + # * Make a new client, with a data store, and close. Then, load a new + # client with the same datastore, and see if the data store loads OK. + # * Make a new client, with a data store, and close. Then, load a new + # client with the same datastore. Delete an entry, and the cookie. + # Start the sync, and everything should sync up OK. + # * Load the refreshOnly client, using existing data. Make a change + # on the server, and the client should pick it up in the refresh phase. + # * Load the refreshAndPersist client, using existing data. Make a change + # on the server, and the client should pick it up in the refresh phase. + # * Load the refreshAndPersist client, using existing data. Let the + # refresh phase complete. Make a change on the server, and the client + # should pick it up during the persist phase. + + +if __name__ == '__main__': + unittest.main() diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 60e9bdb7..d55d0181 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -5,8 +5,20 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + +import sys + +if sys.version_info[0] <= 2: + PY2 = True + text_type = unicode +else: + PY2 = False + text_type = str + import os import unittest +import pickle from slapdtest import SlapdTestCase # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -27,6 +39,12 @@ cn: %(rootcn)s userPassword: %(rootpw)s +dn: cn=user1,%(suffix)s +objectClass: applicationProcess +objectClass: simpleSecurityObject +cn: user1 +userPassword: user1_pw + dn: cn=Foo1,%(suffix)s objectClass: organizationalRole cn: Foo1 @@ -50,7 +68,7 @@ """ -class Test01_SimpleLDAPObject(SlapdTestCase): +class Test00_SimpleLDAPObject(SlapdTestCase): """ test LDAP search operations """ @@ -59,7 +77,7 @@ class Test01_SimpleLDAPObject(SlapdTestCase): @classmethod def setUpClass(cls): - SlapdTestCase.setUpClass() + super(Test00_SimpleLDAPObject, cls).setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -76,9 +94,103 @@ def setUp(self): self._ldap_conn except AttributeError: # open local LDAP connection - self._ldap_conn = self._open_ldap_conn() + self._ldap_conn = self._open_ldap_conn(bytes_mode=False) + + def test_reject_bytes_base(self): + base = self.server.suffix + l = self._ldap_conn + + with self.assertRaises(TypeError): + l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', ['*']) + with self.assertRaises(TypeError): + l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) + with self.assertRaises(TypeError): + l.search_s(base, ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) + + def test_search_keys_are_text(self): + base = self.server.suffix + l = self._ldap_conn + result = l.search_s(base, ldap.SCOPE_SUBTREE, '(cn=Foo*)', ['*']) + result.sort() + dn, fields = result[0] + self.assertEqual(dn, 'cn=Foo1,%s' % base) + self.assertEqual(type(dn), text_type) + for key, values in fields.items(): + self.assertEqual(type(key), text_type) + for value in values: + self.assertEqual(type(value), bytes) + + def _get_bytes_ldapobject(self, explicit=True): + if explicit: + kwargs = {'bytes_mode': True} + else: + kwargs = {} + return self._open_ldap_conn( + who=self.server.root_dn.encode('utf-8'), + cred=self.server.root_pw.encode('utf-8'), + **kwargs + ) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_bytesmode_search_requires_bytes(self): + l = self._get_bytes_ldapobject() + base = self.server.suffix + + with self.assertRaises(TypeError): + l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) + with self.assertRaises(TypeError): + l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) + with self.assertRaises(TypeError): + l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_bytesmode_search_results_have_bytes(self): + l = self._get_bytes_ldapobject() + base = self.server.suffix + result = l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) + result.sort() + dn, fields = result[0] + self.assertEqual(dn, b'cn=Foo1,%s' % base) + self.assertEqual(type(dn), bytes) + for key, values in fields.items(): + self.assertEqual(type(key), bytes) + for value in values: + self.assertEqual(type(value), bytes) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_unset_bytesmode_search_warns_bytes(self): + l = self._get_bytes_ldapobject(explicit=False) + base = self.server.suffix + + l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, '(cn=Foo*)', [b'*']) + l.search_s(base.encode('utf-8'), ldap.SCOPE_SUBTREE, b'(cn=Foo*)', ['*']) + l.search_s(base, ldap.SCOPE_SUBTREE, b'(cn=Foo*)', [b'*']) + + def test_search_accepts_unicode_dn(self): + base = self.server.suffix + l = self._ldap_conn + + with self.assertRaises(ldap.NO_SUCH_OBJECT): + result = l.search_s("CN=abc\U0001f498def", ldap.SCOPE_SUBTREE) - def test_search_subtree(self): + def test_filterstr_accepts_unicode(self): + l = self._ldap_conn + base = self.server.suffix + result = l.search_s(base, ldap.SCOPE_SUBTREE, '(cn=abc\U0001f498def)', ['*']) + self.assertEqual(result, []) + + def test_attrlist_accepts_unicode(self): + base = self.server.suffix + result = self._ldap_conn.search_s( + base, ldap.SCOPE_SUBTREE, + '(cn=Foo*)', ['abc', 'abc\U0001f498def']) + result.sort() + + for dn, attrs in result: + self.assertIsInstance(dn, text_type) + self.assertEqual(attrs, {}) + + def test001_search_subtree(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_SUBTREE, @@ -91,24 +203,24 @@ def test_search_subtree(self): [ ( 'cn=Foo1,'+self.server.suffix, - {'cn': ['Foo1'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo1'], 'objectClass': [b'organizationalRole']} ), ( 'cn=Foo2,'+self.server.suffix, - {'cn': ['Foo2'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo2'], 'objectClass': [b'organizationalRole']} ), ( 'cn=Foo3,'+self.server.suffix, - {'cn': ['Foo3'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo3'], 'objectClass': [b'organizationalRole']} ), ( 'cn=Foo4,ou=Container,'+self.server.suffix, - {'cn': ['Foo4'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo4'], 'objectClass': [b'organizationalRole']} ), ] ) - def test_search_onelevel(self): + def test002_search_onelevel(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_ONELEVEL, @@ -121,20 +233,20 @@ def test_search_onelevel(self): [ ( 'cn=Foo1,'+self.server.suffix, - {'cn': ['Foo1'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo1'], 'objectClass': [b'organizationalRole']} ), ( 'cn=Foo2,'+self.server.suffix, - {'cn': ['Foo2'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo2'], 'objectClass': [b'organizationalRole']} ), ( 'cn=Foo3,'+self.server.suffix, - {'cn': ['Foo3'], 'objectClass': ['organizationalRole']} + {'cn': [b'Foo3'], 'objectClass': [b'organizationalRole']} ), ] ) - def test_search_oneattr(self): + def test003_search_oneattr(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_SUBTREE, @@ -144,15 +256,28 @@ def test_search_oneattr(self): result.sort() self.assertEqual( result, - [('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': ['Foo4']})] + [('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': [b'Foo4']})] ) - def test_errno107(self): + def test_search_subschema(self): + l = self._ldap_conn + dn = l.search_subschemasubentry_s() + self.assertIsInstance(dn, text_type) + self.assertEqual(dn, "cn=Subschema") + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_search_subschema_have_bytes(self): + l = self._get_bytes_ldapobject(explicit=False) + dn = l.search_subschemasubentry_s() + self.assertIsInstance(dn, bytes) + self.assertEqual(dn, b"cn=Subschema") + + def test004_errno107(self): l = self.ldap_object_class('ldap://127.0.0.1:42') try: m = l.simple_bind_s("", "") r = l.result4(m, ldap.MSG_ALL, self.timeout) - except ldap.SERVER_DOWN, ldap_err: + except ldap.SERVER_DOWN as ldap_err: errno = ldap_err.args[0]['errno'] if errno != 107: self.fail("expected errno=107, got %d" % errno) @@ -162,7 +287,7 @@ def test_errno107(self): else: self.fail("expected SERVER_DOWN, got %r" % r) - def test_invalid_credentials(self): + def test005_invalid_credentials(self): l = self.ldap_object_class(self.server.ldap_uri) # search with invalid filter try: @@ -173,7 +298,7 @@ def test_invalid_credentials(self): else: self.fail("expected INVALID_CREDENTIALS, got %r" % r) - def test_sasl_extenal_bind_s(self): + def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() self.assertEqual(l.whoami_s(), 'dn:'+self.server.root_dn.lower()) @@ -181,15 +306,75 @@ def test_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s(authz_id=authz_id) self.assertEqual(l.whoami_s(), authz_id.lower()) + + def test007_timeout(self): + l = self.ldap_object_class(self.server.ldap_uri) + m = l.search_ext(self.server.suffix, ldap.SCOPE_SUBTREE, '(objectClass=*)') + l.abandon(m) + with self.assertRaises(ldap.TIMEOUT): + result = l.result(m, timeout=0.001) -class Test02_ReconnectLDAPObject(Test01_SimpleLDAPObject): +class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ - test LDAP search operations + test ReconnectLDAPObject by restarting slapd """ ldap_object_class = ReconnectLDAPObject + def test101_reconnect_sasl_external(self): + l = self.ldap_object_class(self.server.ldapi_uri) + l.sasl_external_bind_s() + authz_id = l.whoami_s() + self.assertEqual(authz_id, 'dn:'+self.server.root_dn.lower()) + self.server.restart() + self.assertEqual(l.whoami_s(), authz_id) + + def test102_reconnect_simple_bind(self): + l = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) + self.server.restart() + self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) + + def test103_reconnect_get_state(self): + l1 = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + self.assertEqual( + l1.__getstate__(), + { + str('_last_bind'): ( + 'simple_bind_s', + (bind_dn, 'user1_pw'), + {} + ), + str('_options'): [(17, 3)], + str('_reconnects_done'): 0, + str('_retry_delay'): 60.0, + str('_retry_max'): 1, + str('_start_tls'): 0, + str('_trace_level'): 0, + str('_trace_stack_limit'): 5, + str('_uri'): self.server.ldapi_uri, + str('bytes_mode'): l1.bytes_mode, + str('bytes_mode_hardfail'): l1.bytes_mode_hardfail, + str('timeout'): -1, + }, + ) + + def test104_reconnect_restore(self): + l1 = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + l1_state = pickle.dumps(l1) + del l1 + l2 = pickle.loads(l1_state) + self.assertEqual(l2.whoami_s(), 'dn:'+bind_dn) + if __name__ == '__main__': unittest.main() diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index c87b752d..2be03f63 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -5,8 +5,10 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + import unittest -import urllib +from ldap.compat import quote import ldapurl from ldapurl import LDAPUrl @@ -178,16 +180,16 @@ def test_ldapurl(self): class TestLDAPUrl(unittest.TestCase): def assertNone(self, expr, msg=None): - self.failIf(expr is not None, msg or ("%r" % expr)) + self.assertFalse(expr is not None, msg or ("%r" % expr)) def test_combo(self): u = MyLDAPUrl( "ldap://127.0.0.1:1234/dc=example,dc=com" + "?attr1,attr2,attr3" + "?sub" - + "?" + urllib.quote("(objectClass=*)") - + "?bindname=" + urllib.quote("cn=d,c=au") - + ",X-BINDPW=" + urllib.quote("???") + + "?" + quote("(objectClass=*)") + + "?bindname=" + quote("cn=d,c=au") + + ",X-BINDPW=" + quote("???") + ",trace=8" ) self.assertEqual(u.urlscheme, "ldap") @@ -274,7 +276,7 @@ def test_parse_dn(self): u = LDAPUrl("ldap:///dn=foo%3f") self.assertEqual(u.dn, "dn=foo?") u = LDAPUrl("ldap:///dn=str%c3%b6der.com") - self.assertEqual(u.dn, "dn=str\xc3\xb6der.com") + self.assertEqual(u.dn, "dn=str\xf6der.com") def test_parse_attrs(self): u = LDAPUrl("ldap:///?") @@ -338,7 +340,7 @@ def test_parse_filter(self): u = LDAPUrl("ldap:///???(cn=Q%3f)") self.assertEqual(u.filterstr, "(cn=Q?)") u = LDAPUrl("ldap:///???(sn=Str%c3%b6der)") # (possibly bad?) - self.assertEqual(u.filterstr, "(sn=Str\xc3\xb6der)") + self.assertEqual(u.filterstr, "(sn=Str\xf6der)") u = LDAPUrl("ldap:///???(sn=Str\\c3\\b6der)") self.assertEqual(u.filterstr, "(sn=Str\\c3\\b6der)") # (recommended) u = LDAPUrl("ldap:///???(cn=*\\2a*)") diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 4898f765..adf0d262 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + # from Python's standard lib import unittest import textwrap @@ -184,7 +186,7 @@ def test_folded(self): value attrib2: %s - """ % (b'asdf.'*20), [ + """ % ('asdf.'*20), [ ( 'cn=x,cn=y,cn=z', { @@ -248,7 +250,51 @@ def test_binary2(self): ] ) + def test_big_binary(self): + self.check_records( + """ + dn: cn=x,cn=y,cn=z + attrib:: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + = + + """, + [ + ( + 'cn=x,cn=y,cn=z', + {'attrib': [500*b'\0']}, + ), + ] + ) + def test_unicode(self): + # Encode "Ströder" as UTF-8+Base64 + # Putting "Ströder" in a single line would be an invalid LDIF file + # per https://tools.ietf.org/html/rfc2849 (only safe ascii is allowed in a file) + self.check_records( + """ + dn: cn=Michael Stroeder,dc=stroeder,dc=com + lastname:: U3Ryw7ZkZXI= + + """, + [ + ( + 'cn=Michael Stroeder,dc=stroeder,dc=com', + {'lastname': [b'Str\303\266der']}, + ), + ] + ) + + def test_unencoded_unicode(self): + # Encode "Ströder" as UTF-8, without base64 + # This is an invalid LDIF file, but such files are often found in the wild. self.check_records( """ dn: cn=Michael Stroeder,dc=stroeder,dc=com @@ -638,10 +684,10 @@ def test_bad_change_records(self): ldif_string = textwrap.dedent(bad_ldif_string).lstrip() + '\n' try: res = self._parse_records(ldif_string) - except ValueError, value_error: + except ValueError as value_error: pass else: - self.fail("should have raised ValueError: %r" % ldif_str) + self.fail("should have raised ValueError: %r" % bad_ldif_string) def test_mod_increment(self): self.check_records( diff --git a/distclean.sh b/distclean.sh new file mode 100755 index 00000000..25851165 --- /dev/null +++ b/distclean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +python setup.py clean +rm -r MANIFEST dist/* build/* Lib/*.egg-info .tox +rm Lib/_ldap.so Lib/*.py? Lib/ldap/*.py? Lib/ldap/*/*.py? Tests/*.py? *.py? diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..465a037f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pyasn1>=0.3.7 +pyasn1_modules>=0.1.5 + diff --git a/setup.cfg b/setup.cfg index 10dafc93..59816b72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ optimize = 1 provides = python-ldap requires = python libldap-2_4 vendor = python-ldap project -packager = Michael Ströder +packager = python-ldap team distribution_name = openSUSE 11.x release = 1 doc_files = CHANGES README INSTALL TODO Demo/ diff --git a/setup.py b/setup.py index 7318c41b..89351b5c 100644 --- a/setup.py +++ b/setup.py @@ -4,6 +4,8 @@ See https://www.python-ldap.org/ for details. """ +import sys,os + has_setuptools = False try: from setuptools import setup, Extension @@ -11,23 +13,18 @@ except ImportError: from distutils.core import setup, Extension -from ConfigParser import ConfigParser -import sys,os,string,time - -################################################################## -# Weird Hack to grab release version of python-ldap from local dir -################################################################## -exec_startdir = os.path.dirname(os.path.abspath(sys.argv[0])) -package_init_file_name = reduce(os.path.join,[exec_startdir,'Lib','ldap','__init__.py']) -f = open(package_init_file_name,'r') -s = f.readline() -while s: - s = string.strip(s) - if s[0:11]=='__version__': - version = eval(string.split(s,'=')[1]) - break - s = f.readline() -f.close() +if sys.version_info[0] == 2 and sys.version_info[1] < 7: + raise RuntimeError('This software requires Python 2.7 or 3.x.') +if sys.version_info[0] >= 3 and sys.version_info < (3, 3): + raise RuntimeError('The C API from Python 3.3+ is required.') + +if sys.version_info[0] >= 3: + from configparser import ConfigParser +else: + from ConfigParser import ConfigParser + +sys.path.insert(0, os.path.join(os.getcwd(), 'Lib/ldap')) +import pkginfo #-- A class describing the features and requirements of OpenLDAP 2.0 class OpenLDAP2: @@ -48,15 +45,14 @@ class OpenLDAP2: if cfg.has_section('_ldap'): for name in dir(LDAP_CLASS): if cfg.has_option('_ldap', name): - print name + ': ' + cfg.get('_ldap', name) - setattr(LDAP_CLASS, name, string.split(cfg.get('_ldap', name))) + setattr(LDAP_CLASS, name, cfg.get('_ldap', name).split()) for i in range(len(LDAP_CLASS.defines)): LDAP_CLASS.defines[i]=((LDAP_CLASS.defines[i],None)) for i in range(len(LDAP_CLASS.extra_files)): - destdir, origfiles = string.split(LDAP_CLASS.extra_files[i], ':') - origfileslist = string.split(origfiles, ',') + destdir, origfiles = LDAP_CLASS.extra_files[i].split(':') + origfileslist = origfiles.split(',') LDAP_CLASS.extra_files[i]=(destdir, origfileslist) #-- Let distutils/setuptools do the rest @@ -72,13 +68,15 @@ class OpenLDAP2: kwargs = dict( include_package_data = True, install_requires = ['setuptools'], - zip_safe = False + zip_safe = False, + python_requires = '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*', ) setup( #-- Package description name = name, - version = version, + license=pkginfo.__license__, + version=pkginfo.__version__, description = 'Python modules for implementing LDAP clients', long_description = """python-ldap: python-ldap provides an object-oriented API to access LDAP directory servers @@ -100,15 +98,23 @@ class OpenLDAP2: 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', 'Programming Language :: C', + 'Programming Language :: Python', 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + # Note: when updating Python versions, also change .travis.yml and tox.ini + 'Topic :: Database', 'Topic :: Internet', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP', 'License :: OSI Approved :: Python Software Foundation License', ], - license = 'Python style', #-- C extension modules ext_modules = [ Extension( @@ -120,10 +126,8 @@ class OpenLDAP2: 'Modules/constants.c', 'Modules/errors.c', 'Modules/functions.c', - 'Modules/schema.c', 'Modules/ldapmodule.c', 'Modules/message.c', - 'Modules/version.c', 'Modules/options.c', 'Modules/berval.c', ], @@ -138,17 +142,21 @@ class OpenLDAP2: ('ldap_r' in LDAP_CLASS.libs or 'oldap_r' in LDAP_CLASS.libs)*[('HAVE_LIBLDAP_R',None)] + \ ('sasl' in LDAP_CLASS.libs or 'sasl2' in LDAP_CLASS.libs or 'libsasl' in LDAP_CLASS.libs)*[('HAVE_SASL',None)] + \ ('ssl' in LDAP_CLASS.libs and 'crypto' in LDAP_CLASS.libs)*[('HAVE_TLS',None)] + \ - [('LDAPMODULE_VERSION', version)] + [ + ('LDAPMODULE_VERSION', pkginfo.__version__), + ('LDAPMODULE_AUTHOR', pkginfo.__author__), + ('LDAPMODULE_LICENSE', pkginfo.__license__), + ] ), ], #-- Python "stand alone" modules py_modules = [ 'ldapurl', 'ldif', - 'dsml', 'ldap', 'slapdtest', 'ldap.async', + 'ldap.compat', 'ldap.controls', 'ldap.controls.deref', 'ldap.controls.libldap', @@ -160,6 +168,7 @@ class OpenLDAP2: 'ldap.controls.sessiontrack', 'ldap.controls.simple', 'ldap.controls.sss', + 'ldap.controls.vlv', 'ldap.cidict', 'ldap.dn', 'ldap.extop', @@ -169,6 +178,7 @@ class OpenLDAP2: 'ldap.ldapobject', 'ldap.logger', 'ldap.modlist', + 'ldap.pkginfo', 'ldap.resiter', 'ldap.sasl', 'ldap.schema', diff --git a/tox.ini b/tox.ini index d11ce1c7..61a6aed9 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,8 @@ # and then run "tox" from this directory. [tox] -envlist = py27 +# Note: when updating Python versions, also change setup.py and .travis.yml +envlist = py27,py33,py34,py35,py36 [testenv] commands = {envpython} setup.py test pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy