From aeeac962b7e122e3ab0465658e9148beea00d60f Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 16:16:47 +0100 Subject: [PATCH 01/30] Infra: Condense CHANGELOG from 2.5 --- CHANGES | 55 +++++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index b3ef0d5..92b4737 100644 --- a/CHANGES +++ b/CHANGES @@ -1,36 +1,5 @@ ---------------------------------------------------------------- -Released 2.5.2 2017-11-20 - -Changes since 2.5.1: - -Modules/ -* PyBytes_ instead of PyString_ and added PyInt_FromLong compat macro -* 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/ -* 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 - -Tests/ -* 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) - ----------------------------------------------------------------- -Released 2.5.1 2017-11-12 +Released 3.0.0 xxxx-xx-xx Changes since 2.4.45: @@ -39,9 +8,16 @@ Mandatory prerequisites: - pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ 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: @@ -58,9 +34,24 @@ Lib/ 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 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) ---------------------------------------------------------------- Released 2.4.45 2017-10-09 From 83c7235f926457266e323a98c083723ea294339e Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 15:36:42 +0100 Subject: [PATCH 02/30] Infra: Add a .gitignore file --- .gitignore | 17 +++++++++++++++++ CHANGES | 3 +++ 2 files changed, 20 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..072e203 --- /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/CHANGES b/CHANGES index 92b4737..a8ad44a 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,9 @@ Mandatory prerequisites: - Python 2.7.x - pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ +Infrastructure: +- Add .gitignore + Modules/ (thanks to Michael Ströder) * removed unused code schema.c From e73f8d8dedb7840371b9253f797f1fdb17cc32ea Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:11:44 +0100 Subject: [PATCH 03/30] py3: Use print as a function rather than statement --- Demo/Lib/ldap/async/deltree.py | 4 +++- Demo/Lib/ldapurl/urlsearch.py | 10 ++++---- Demo/initialize.py | 21 +++++++++-------- Demo/ldapcontrols.py | 9 ++++---- Demo/ldapurl_search.py | 6 +++-- Demo/matchedvalues.py | 13 ++++++----- Demo/options.py | 29 ++++++++++++----------- Demo/page_control.py | 17 +++++++------- Demo/paged_search_ext_s.py | 3 ++- Demo/passwd_ext_op.py | 5 ++-- Demo/pyasn1/dds.py | 13 ++++++----- Demo/pyasn1/derefcontrol.py | 7 +++--- Demo/pyasn1/noopsearch.py | 11 +++++---- Demo/pyasn1/ppolicy.py | 15 ++++++------ Demo/pyasn1/psearch.py | 11 +++++---- Demo/pyasn1/readentrycontrol.py | 41 +++++++++++++++++---------------- Demo/pyasn1/sessiontrack.py | 8 ++++--- Demo/pyasn1/syncrepl.py | 3 ++- Demo/rename.py | 5 ++-- Demo/resiter.py | 3 ++- Demo/sasl_bind.py | 13 ++++++----- Demo/schema.py | 39 ++++++++++++++++--------------- Demo/schema_tree.py | 39 ++++++++++++++++--------------- Demo/simple.py | 7 +++--- Demo/simplebrowse.py | 39 ++++++++++++++++--------------- Doc/ldap-resiter.rst | 2 +- Doc/ldap.rst | 2 +- 27 files changed, 201 insertions(+), 174 deletions(-) diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 58df3b3..04506b3 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): @@ -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/ldapurl/urlsearch.py b/Demo/Lib/ldapurl/urlsearch.py index 996e6da..b293aa2 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/initialize.py b/Demo/initialize.py index ab2647e..952b3f4 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 214042d..a5ba8d3 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 2c63009..07ffbca 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 4de3e8a..59c594f 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 fb37209..8b4e215 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 0fc904b..8238ede 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 d0e0982..457d79c 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=*)' @@ -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 1030f0e..cc5d22c 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 656b439..71722b0 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 @@ -17,7 +18,7 @@ ldap_url = ldapurl.LDAPUrl(sys.argv[1]) request_ttl = int(sys.argv[2]) except IndexError,ValueError: - print 'Usage: dds.py ' + print('Usage: dds.py ') sys.exit(1) # Set debugging level @@ -32,14 +33,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 or '',ldap_url.cred or '') except ldap.INVALID_CREDENTIALS,e: - print 'Simple bind failed:',str(e) + print('Simple bind failed:',str(e)) sys.exit(1) else: @@ -47,9 +48,9 @@ try: extop_resp_obj = ldap_conn.extop_s(extreq,extop_resp_class=RefreshResponse) except ldap.LDAPError,e: - print str(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 1098529..0e7153d 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 08635de..88d2d89 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) + print('Simple bind failed:',str(e)) sys.exit(1) try: @@ -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 8722356..97720b0 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 @@ -17,7 +18,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) except IndexError,ValueError: - print 'Usage: ppolicy.py ' + 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) + 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 02852b1..b2300ad 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) + 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 b52ea91..10faa2b 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 1a1eab1..337c5e9 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 @@ -17,7 +19,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) except IndexError,ValueError: - print 'Usage: %s ' % (sys.argv[0]) + 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) + 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 1454eef..479a626 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -8,6 +8,7 @@ The bound user needs read access to the attributes entryDN and entryCSN. """ +from __future__ import print_function # Import modules from Python standard lib import logging @@ -157,7 +158,7 @@ def commenceShutdown(signum, stack): ).format(script_name=sys.argv[0]) sys.exit(1) except ValueError,e: - print 'Error parsing command-line arguments:', str(e) + print('Error parsing command-line arguments:',str(e)) sys.exit(1) while watcher_running: diff --git a/Demo/rename.py b/Demo/rename.py index 1bb6fd7..91d7528 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 ff9fe5a..d6796ba 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -4,6 +4,7 @@ See http://www.python-ldap.org for details. """ +from __future__ import print_function import ldap,ldap.resiter @@ -16,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 05af652..7ff75bf 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,7 +61,7 @@ ), ]: 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! @@ -68,15 +69,15 @@ try: l.sasl_interactive_bind_s("", sasl_auth) except ldap.LDAPError,e: - print 'Error using SASL mechanism',sasl_auth.mech,str(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()) + 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('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 c8103f3..9f650d2 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,39 +10,39 @@ 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]), @@ -49,15 +50,15 @@ ] ) except KeyError,e: - print '***KeyError',str(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 3b3a091..ef8bcec 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]' + 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 e4cf4d0..6004c9e 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 804d12f..aa88f67 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -3,6 +3,7 @@ # # simple LDAP server browsing example # +from __future__ import print_function import ldap from traceback import print_exc @@ -10,7 +11,7 @@ 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); @@ -30,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 @@ -58,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": @@ -79,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 @@ -93,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: @@ -104,7 +105,7 @@ ("... (%d bytes)" % len(v)) else: v = `v` - print " %-12s: %s" % (k, v) + print(" %-12s: %s" % (k, v)) elif cmd.startswith("/"): # @@ -114,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/ldap-resiter.rst b/Doc/ldap-resiter.rst index 0beabb8..0e72e92 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 e983f64..5160f30 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) From 17365cc6045b8c7ce9f5f3292ac592f8dc8e5448 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:17:56 +0100 Subject: [PATCH 04/30] py3: Use new syntax for exceptions except ExcType, value -> except ExcType as value raise ExcType, value -> raise ExcType(value) raise value -> raise (when re-raising) --- Demo/Lib/ldap/async/deltree.py | 4 ++-- Demo/paged_search_ext_s.py | 2 +- Demo/pyasn1/dds.py | 6 +++--- Demo/pyasn1/noopsearch.py | 4 ++-- Demo/pyasn1/ppolicy.py | 4 ++-- Demo/pyasn1/psearch.py | 2 +- Demo/pyasn1/sessiontrack.py | 4 ++-- Demo/pyasn1/syncrepl.py | 6 +++--- Demo/sasl_bind.py | 4 ++-- Demo/schema.py | 2 +- Demo/schema_tree.py | 2 +- Lib/ldap/controls/__init__.py | 4 ++-- Lib/ldap/controls/openldap.py | 2 +- Lib/ldap/functions.py | 2 +- Lib/ldap/ldapobject.py | 10 +++++----- Lib/ldap/schema/subentry.py | 2 +- Lib/ldif.py | 2 +- Tests/t_cext.py | 4 ++-- Tests/t_ldapobject.py | 2 +- Tests/t_ldif.py | 2 +- 20 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 04506b3..9e23f1c 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -17,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( @@ -47,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 diff --git a/Demo/paged_search_ext_s.py b/Demo/paged_search_ext_s.py index 457d79c..d0f8291 100644 --- a/Demo/paged_search_ext_s.py +++ b/Demo/paged_search_ext_s.py @@ -73,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: diff --git a/Demo/pyasn1/dds.py b/Demo/pyasn1/dds.py index 71722b0..c803a1d 100644 --- a/Demo/pyasn1/dds.py +++ b/Demo/pyasn1/dds.py @@ -17,7 +17,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) request_ttl = int(sys.argv[2]) -except IndexError,ValueError: +except (IndexError, ValueError): print('Usage: dds.py ') sys.exit(1) @@ -39,7 +39,7 @@ try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: +except ldap.INVALID_CREDENTIALS as e: print('Simple bind failed:',str(e)) sys.exit(1) @@ -47,7 +47,7 @@ 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: + except ldap.LDAPError as e: print(str(e)) else: if extop_resp_obj.responseTtl!=request_ttl: diff --git a/Demo/pyasn1/noopsearch.py b/Demo/pyasn1/noopsearch.py index 88d2d89..2045f50 100644 --- a/Demo/pyasn1/noopsearch.py +++ b/Demo/pyasn1/noopsearch.py @@ -41,7 +41,7 @@ try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: +except ldap.INVALID_CREDENTIALS as e: print('Simple bind failed:',str(e)) sys.exit(1) @@ -59,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) diff --git a/Demo/pyasn1/ppolicy.py b/Demo/pyasn1/ppolicy.py index 97720b0..cf6b2ac 100644 --- a/Demo/pyasn1/ppolicy.py +++ b/Demo/pyasn1/ppolicy.py @@ -17,7 +17,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) -except IndexError,ValueError: +except (IndexError,ValueError): print('Usage: ppolicy.py ') sys.exit(1) @@ -39,7 +39,7 @@ 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: +except ldap.INVALID_CREDENTIALS as e: print('Simple bind failed:',str(e)) sys.exit(1) else: diff --git a/Demo/pyasn1/psearch.py b/Demo/pyasn1/psearch.py index b2300ad..3bd59e6 100644 --- a/Demo/pyasn1/psearch.py +++ b/Demo/pyasn1/psearch.py @@ -40,7 +40,7 @@ try: ldap_conn.simple_bind_s(ldap_url.who,ldap_url.cred) -except ldap.INVALID_CREDENTIALS,e: +except ldap.INVALID_CREDENTIALS as e: print('Simple bind failed:',str(e)) sys.exit(1) diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 337c5e9..91909a3 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -18,7 +18,7 @@ try: ldap_url = ldapurl.LDAPUrl(sys.argv[1]) -except IndexError,ValueError: +except (IndexError, ValueError): print('Usage: %s ' % (sys.argv[0])) sys.exit(1) @@ -40,7 +40,7 @@ try: ldap_conn.simple_bind_s(ldap_url.who or '',ldap_url.cred or '') -except ldap.INVALID_CREDENTIALS,e: +except ldap.INVALID_CREDENTIALS as e: print('Simple bind failed:',str(e)) sys.exit(1) diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 479a626..e4c62e8 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -157,7 +157,7 @@ def commenceShutdown(signum, stack): 'X-BINDPW=password" db.shelve' ).format(script_name=sys.argv[0]) sys.exit(1) -except ValueError,e: +except ValueError as e: print('Error parsing command-line arguments:',str(e)) sys.exit(1) @@ -169,7 +169,7 @@ def commenceShutdown(signum, stack): # Now we login to the LDAP server try: ldap_connection.simple_bind_s(ldap_url.who, ldap_url.cred) - except ldap.INVALID_CREDENTIALS, err: + except ldap.INVALID_CREDENTIALS as err: logger.error('Login to LDAP server failed: %s', err) sys.exit(1) except ldap.SERVER_DOWN: @@ -193,7 +193,7 @@ def commenceShutdown(signum, stack): except KeyboardInterrupt: # User asked to exit commenceShutdown(None, None) - except Exception, err: + except Exception as err: # Handle any exception if watcher_running: logger.exception('Unhandled exception, going to retry: %s', err) diff --git a/Demo/sasl_bind.py b/Demo/sasl_bind.py index 7ff75bf..667221c 100644 --- a/Demo/sasl_bind.py +++ b/Demo/sasl_bind.py @@ -68,13 +68,13 @@ l.protocol_version = 3 try: l.sasl_interactive_bind_s("", sasl_auth) - except ldap.LDAPError,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) try: print('Result of Who Am I? ext. op:',repr(l.whoami_s())) - except ldap.LDAPError,e: + 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))) diff --git a/Demo/schema.py b/Demo/schema.py index 9f650d2..4d350f0 100644 --- a/Demo/schema.py +++ b/Demo/schema.py @@ -49,7 +49,7 @@ ('usage',range(2)), ] ) -except KeyError,e: +except KeyError as e: print('***KeyError',str(e)) diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index ef8bcec..79c8a83 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -52,7 +52,7 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): try: options,args=getopt.getopt(sys.argv[1:],'',['html']) -except getopt.error,e: +except getopt.error: print('Error: %s\nUsage: schema_oc_tree.py [--html] [LDAP URL]') html_output = options and options[0][0]=='--html' diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index 56d611b..932fa53 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -148,9 +148,9 @@ def DecodeControlTuples(ldapControlTuples,knownLDAPControls=None): control.controlType,control.criticality = controlType,criticality try: control.decodeControlValue(encodedControlValue) - except PyAsn1Error,e: + except PyAsn1Error: if criticality: - raise e + raise else: result.append(control) return result diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py index ee7b575..7108c63 100644 --- a/Lib/ldap/controls/openldap.py +++ b/Lib/ldap/controls/openldap.py @@ -67,7 +67,7 @@ def noop_search_st(self,base,scope=ldap.SCOPE_SUBTREE,filterstr='(objectClass=*) ldap.TIMELIMIT_EXCEEDED, ldap.SIZELIMIT_EXCEEDED, ldap.ADMINLIMIT_EXCEEDED - ),e: + ) as e: self.abandon(msg_id) raise e else: diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 6ddab54..c60d42b 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -53,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 diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 805b6e3..a199462 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -96,7 +96,7 @@ 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 'info' not in e.args[0] and 'errno' in e.args[0]: @@ -125,9 +125,9 @@ def __getattr__(self,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): """ @@ -865,14 +865,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) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 99a0dc4..3612a38 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -430,7 +430,7 @@ def attribute_types( 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] diff --git a/Lib/ldif.py b/Lib/ldif.py index 86b8ac9..714d01c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -335,7 +335,7 @@ def _next_key_and_value(self): return '-',None try: colon_pos = unfolded_line.index(':') - except ValueError,e: + except ValueError as e: raise ValueError('no value-spec in %s' % (repr(unfolded_line))) attr_type = unfolded_line[0:colon_pos] # if needed attribute value is BASE64 decoded diff --git a/Tests/t_cext.py b/Tests/t_cext.py index cac661e..7c1417b 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -253,7 +253,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) @@ -683,7 +683,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_ldapobject.py b/Tests/t_ldapobject.py index 196a9f3..5773ff2 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -159,7 +159,7 @@ def test004_errno107(self): 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) diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 3da213f..76701cc 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -662,7 +662,7 @@ 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" % bad_ldif_string) From 17b4000d7d0eaab602098ac3aa5d858373d659af Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:30:42 +0100 Subject: [PATCH 05/30] py3: Use modern idioms with built-in types d.has_key(x) -> x in d list(x); x.sort() -> sorted(x) filter(lambda...) -> list comprehension d.keys() -> list(d.keys()) (when keys are modified) x == None -> x is None --- Demo/Lib/ldap/async/deltree.py | 2 +- Lib/ldap/modlist.py | 6 +++--- Lib/ldap/schema/subentry.py | 7 ++++--- Lib/ldif.py | 6 +++--- Tests/t_cidict.py | 6 ++---- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index 9e23f1c..68d3643 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -30,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( diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 6f4bf1e..a853500 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -18,7 +18,7 @@ def addModlist(entry,ignore_attr_types=None): # 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() @@ -58,10 +58,10 @@ def modifyModlist( # This attribute type is ignored continue # Filter away null-strings - new_value = filter(lambda x:x!=None,new_entry[attrtype]) + 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 = [] diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 3612a38..fbc33c5 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -294,7 +294,8 @@ 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]: @@ -417,14 +418,14 @@ def attribute_types( # Remove all mandantory attribute types from # optional attribute type list - for a in r_may.keys(): + 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] diff --git a/Lib/ldif.py b/Lib/ldif.py index 714d01c..318f46c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -125,7 +125,7 @@ def _needs_base64_encoding(self,attr_type,attr_value): returns 1 if attr_value has to be base-64 encoded because of special chars or because attr_type is in self._base64_attrs """ - return self._base64_attrs.has_key(attr_type.lower()) or \ + return attr_type.lower() in self._base64_attrs or \ not safe_string_re.search(attr_value) is None def _unparseAttrTypeandValue(self,attr_type,attr_value): @@ -351,7 +351,7 @@ def _next_key_and_value(self): attr_value = None if self._process_url_schemes: u = urlparse.urlparse(url) - if self._process_url_schemes.has_key(u[0]): + if u[0] in self._process_url_schemes: attr_value = urllib.urlopen(url).read() else: attr_value = unfolded_line[colon_pos+1:] @@ -369,7 +369,7 @@ def _consume_empty_lines(self): # Consume empty lines try: k,v = next_key_and_value() - while k==v==None: + while k is None and v is None: k,v = next_key_and_value() except EOFError: k,v = None,None diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index c8812f2..dea802d 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -32,11 +32,9 @@ 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("abcdef" in cix, False) From 587460a228441dfcc56cba9ce8bebd393c9f24bb Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:42:00 +0100 Subject: [PATCH 06/30] py3: Add and use the ldap.compat module Add a module providing common things that differ between Python 2 and 3. (This is really a limited approximation of the six library) --- Lib/ldap/cidict.py | 2 +- Lib/ldap/compat.py | 43 +++++++++++++++++++++++++++++++++++++ Lib/ldap/ldapobject.py | 6 +++++- Lib/ldap/schema/models.py | 9 +++++--- Lib/ldap/schema/subentry.py | 5 +++-- Lib/ldapurl.py | 8 +++---- Lib/ldif.py | 8 +++---- Lib/slapdtest.py | 5 +++-- Tests/t_ldapurl.py | 8 +++---- setup.py | 1 + 10 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 Lib/ldap/compat.py diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 07832ef..fdfba4b 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -8,7 +8,7 @@ from ldap import __version__ -from UserDict import IterableUserDict +from ldap.compat import IterableUserDict class cidict(IterableUserDict): diff --git a/Lib/ldap/compat.py b/Lib/ldap/compat.py new file mode 100644 index 0000000..de0e110 --- /dev/null +++ b/Lib/ldap/compat.py @@ -0,0 +1,43 @@ +"""Compatibility wrappers for Py2/Py3.""" + +import sys + +if sys.version_info[0] < 3: + from UserDict import UserDict, IterableUserDict + from urllib import quote + from urllib import quote_plus + from urllib import unquote as urllib_unquote + from urllib import urlopen + from urlparse import urlparse + + def unquote(uri): + """Specialized unquote that uses UTF-8 for parsing.""" + uri = uri.encode('ascii') + unquoted = urllib_unquote(uri) + return unquoted.decode('utf-8') + + # Old-style of re-raising an exception is SyntaxError in Python 3, + # so hide behind exec() so the Python 3 parser doesn't see it + exec('''def reraise(exc_type, exc_value, exc_traceback): + """Re-raise an exception given information from sys.exc_info() + + Note that unlike six.reraise, this does not support replacing the + traceback. All arguments must come from a single sys.exc_info() call. + """ + raise exc_type, exc_value, exc_traceback + ''') + +else: + from collections import UserDict + IterableUserDict = UserDict + from urllib.parse import quote, quote_plus, unquote, urlparse + from urllib.request import urlopen + + def reraise(exc_type, exc_value, exc_traceback): + """Re-raise an exception given information from sys.exc_info() + + Note that unlike six.reraise, this does not support replacing the + traceback. All arguments must come from a single sys.exc_info() call. + """ + # In Python 3, all exception info is contained in one object. + raise exc_value diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index a199462..970f014 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -24,6 +24,7 @@ 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 @@ -105,7 +106,10 @@ def _ldap_call(self,func,*args,**kwargs): 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: diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 8471954..c3bb702 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -4,7 +4,10 @@ See https://www.python-ldap.org/ for details. """ -import UserDict,ldap.cidict +import sys + +import ldap.cidict +from ldap.compat import IterableUserDict from ldap.schema.tokenizer import split_tokens,extract_tokens @@ -615,7 +618,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(UserDict.IterableUserDict): +class Entry(IterableUserDict): """ Schema-aware implementation of an LDAP entry class. @@ -628,7 +631,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): diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index fbc33c5..4d42b19 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -473,8 +473,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/ldapurl.py b/Lib/ldapurl.py index bbd6929..366e177 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -16,9 +16,7 @@ 'LDAPUrlExtension','LDAPUrlExtensions','LDAPUrl' ] -import UserDict - -from urllib import quote,unquote +from ldap.compat import UserDict, quote, unquote LDAP_SCOPE_BASE = 0 LDAP_SCOPE_ONELEVEL = 1 @@ -132,14 +130,14 @@ def __ne__(self,other): return not self.__eq__(other) -class LDAPUrlExtensions(UserDict.UserDict): +class LDAPUrlExtensions(UserDict): """ Models a collection of LDAP URL extensions as dictionary type """ def __init__(self,default=None): - UserDict.UserDict.__init__(self) + UserDict.__init__(self) for k,v in (default or {}).items(): self[k]=v diff --git a/Lib/ldif.py b/Lib/ldif.py index 318f46c..7659358 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -18,8 +18,6 @@ 'LDIFCopy', ] -import urlparse -import urllib import re from base64 import b64encode, b64decode @@ -28,6 +26,8 @@ except ImportError: from StringIO import StringIO +from ldap.compat import urlparse, urlopen + attrtype_pattern = r'[\w;.-]+(;[\w_-]+)*' attrvalue_pattern = r'(([^,]|\\,)+|".*?")' attrtypeandvalue_pattern = attrtype_pattern + r'[ ]*=[ ]*' + attrvalue_pattern @@ -350,9 +350,9 @@ def _next_key_and_value(self): url = unfolded_line[colon_pos+2:].strip() attr_value = None if self._process_url_schemes: - u = urlparse.urlparse(url) + u = urlparse(url) if u[0] in self._process_url_schemes: - attr_value = urllib.urlopen(url).read() + attr_value = urlopen(url).read() else: attr_value = unfolded_line[colon_pos+1:] return attr_type,attr_value diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 2f98672..6bb43f0 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -14,7 +14,8 @@ import logging from logging.handlers import SysLogHandler import unittest -import urllib + +from ldap.compat import quote_plus # a template string for generating simple slapd.conf file SLAPD_CONF_TEMPLATE = r""" @@ -125,7 +126,7 @@ def __init__(self): self._db_directory = os.path.join(self.testrundir, "openldap-data") self.ldap_uri = "ldap://%s:%d/" % (LOCALHOST, self._port) ldapi_path = os.path.join(self.testrundir, 'ldapi') - self.ldapi_uri = "ldapi://%s" % urllib.quote_plus(ldapi_path) + self.ldapi_uri = "ldapi://%s" % quote_plus(ldapi_path) def setup_rundir(self): """ diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index c87b752..407538f 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -6,7 +6,7 @@ """ import unittest -import urllib +from ldap.compat import quote import ldapurl from ldapurl import LDAPUrl @@ -185,9 +185,9 @@ def test_combo(self): "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") diff --git a/setup.py b/setup.py index 6bbf595..a475a35 100644 --- a/setup.py +++ b/setup.py @@ -137,6 +137,7 @@ class OpenLDAP2: 'ldap', 'slapdtest', 'ldap.async', + 'ldap.compat', 'ldap.controls', 'ldap.controls.deref', 'ldap.controls.libldap', From 03e8e7e8dc058fcfd8880c861fa9d1d28efe5b8b Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:45:52 +0100 Subject: [PATCH 07/30] py3: Use "int" instead of "long" (in Python code) In Python 2.7, long integers behave sufficiently similarly to normal int. In Python 3, there is only one kind of int. --- Lib/ldap/ldapobject.py | 4 ++-- Lib/ldap/schema/models.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 970f014..e83287d 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -808,7 +808,7 @@ def __init__( 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""" @@ -886,7 +886,7 @@ 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() diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index c3bb702..300981e 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -267,9 +267,9 @@ 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 From b62c5894d882867fc3bfc2dc2aaa54ff3fdcd9f0 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:59:57 +0100 Subject: [PATCH 08/30] py3: Use absolute imports --- Lib/ldap/__init__.py | 4 ++-- Tests/__init__.py | 24 +++++++++++++----------- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index c8a1b12..dab97a6 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -86,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/Tests/__init__.py b/Tests/__init__.py index b7b5378..df95a06 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -5,14 +5,16 @@ 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_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_ldap_schema_subentry From 8a54ae3c3396280fa7a6936f31e8948d95cf8b1c Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:16:50 +0100 Subject: [PATCH 09/30] py3: Import StringIO from io This was backported to Python 2.7. It uses the C-optimized version, so cStringIO is not needed either. --- Lib/ldif.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/ldif.py b/Lib/ldif.py index 7659358..94ffd91 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -20,11 +20,7 @@ import re from base64 import b64encode, b64decode - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO +from io import StringIO from ldap.compat import urlparse, urlopen From 301939e165efadbe93d83eedb87af6efea14754f Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:29:02 +0100 Subject: [PATCH 10/30] Tests: Expand cidict membership tests Note that for backwards compatibility, cidict keeps the has_key method even on Python 3. --- Tests/t_cidict.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index dea802d..00d0726 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -37,9 +37,12 @@ def test_cidict(self): cix_items = sorted(cix.items()) self.assertEqual(cix_items, [('AbCDeF',123), ('xYZ',987)]) del cix["abcdEF"] - self.assertEqual("abcdef" in cix, 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__': From f605dbf25ac1dc0e94fe66eca1a2b8f60b06806c Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:33:06 +0100 Subject: [PATCH 11/30] Infra: Re-format README to Restructured Text, add REMADE.rst symlink The symlink is intended for tools that automatically pretty-print the file. --- README | 99 +++++++++++++++++++++++++++++------------------------- README.rst | 1 + 2 files changed, 54 insertions(+), 46 deletions(-) create mode 120000 README.rst diff --git a/README b/README index d8e88bb..6081b4b 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,85 @@ 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 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 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 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 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 Waldemar Osuch for contributing +the new-style docs based on reStructuredText. - Thanks to Torsten Kurbad for the - easy_install support. +Thanks to Torsten Kurbad for the +easy_install support. - Thanks to James Andrewartha for - significant contribution to Doc/*.tex. +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 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 Peter Gietz, DAASI for funding some control modules. - Thanks to Chris Mikkelson for various fixes and ldap.syncrepl. +Thanks to Chris Mikkelson for various fixes and ldap.syncrepl. - These very kind people have supplied patches or suggested changes: +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 +* 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 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 0000000..100b938 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +README \ No newline at end of file From b2c2b5cb06fc68f1c7ceeac50df20454415be71b Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:37:46 +0100 Subject: [PATCH 12/30] slapdtest: Open slapd config file in text mode when writing it --- Lib/slapdtest.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 6bb43f0..3c6e549 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -214,9 +214,8 @@ def _ln_schema_files(self, file_names, source_dir): def _write_config(self): """Writes the slapd.conf file out, and returns the path to it.""" self._log.debug('Writing config to %s', self._slapd_conf) - config_file = file(self._slapd_conf, 'wb') - config_file.write(self.gen_config()) - config_file.close() + with open(self._slapd_conf, 'w') as config_file: + config_file.write(self.gen_config()) self._log.info('Wrote config to %s', self._slapd_conf) def _test_config(self): From 42f3e84834eb11505d8df77494bfe662d5540746 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:38:40 +0100 Subject: [PATCH 13/30] slapdtest: Automatically try some common locations for SCHEMADIR --- Lib/slapdtest.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 3c6e549..2580ef3 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -105,7 +105,14 @@ class SlapdObject(object): TMPDIR = os.environ.get('TMP', os.getcwd()) SBINDIR = os.environ.get('SBIN', '/usr/sbin') BINDIR = os.environ.get('BIN', '/usr/bin') - SCHEMADIR = os.environ.get('SCHEMA', '/etc/openldap/schema') + if 'SCHEMA' in os.environ: + SCHEMADIR = os.environ['SCHEMA'] + elif os.path.isdir("/etc/openldap/schema"): + SCHEMADIR = "/etc/openldap/schema" + elif os.path.isdir("/etc/ldap/schema"): + SCHEMADIR = "/etc/ldap/schema" + else: + PATH_SCHEMA_CORE = None PATH_LDAPADD = os.path.join(BINDIR, 'ldapadd') PATH_LDAPMODIFY = os.path.join(BINDIR, 'ldapmodify') PATH_LDAPWHOAMI = os.path.join(BINDIR, 'ldapwhoami') From d54539f25bbfb028bfe75c877200b76690eb45d4 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:39:24 +0100 Subject: [PATCH 14/30] slapdtest: Ensure server is stopped when the process exits --- Lib/slapdtest.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 2580ef3..1aba887 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -12,6 +12,7 @@ import time import subprocess import logging +import atexit from logging.handlers import SysLogHandler import unittest @@ -151,6 +152,9 @@ def _cleanup_rundir(self): """ Recursively delete whole directory specified by `path' """ + # cleanup_rundir() is called in atexit handler. Until Python 3.4, + # the rest of the world is already destroyed. + import os, os.path if not os.path.exists(self.testrundir): return self._log.debug('clean-up %s', self.testrundir) @@ -278,6 +282,7 @@ def start(self): if self._proc is None: # prepare directory structure + atexit.register(self.stop) self._cleanup_rundir() self.setup_rundir() self._write_config() From e90c7d15a870f1ac33fa6f177c42830c7d2271d7 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:46:08 +0100 Subject: [PATCH 15/30] Tests: Replace deprecated "failIf" name by "assertFalse" --- Tests/t_cext.py | 4 ++-- Tests/t_ldapurl.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 7c1417b..3cbdbe7 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -64,10 +64,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 diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 407538f..a68b032 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -178,7 +178,7 @@ 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( From ffd35806229eafe373c41a32f645c9c51307f1f9 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 22:55:33 +0100 Subject: [PATCH 16/30] Infra: Remove PKG-INFO --- PKG-INFO | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 PKG-INFO diff --git a/PKG-INFO b/PKG-INFO deleted file mode 100644 index e69de29..0000000 From 39ad22ec7185df86ca79781ad96e03c1f2bba898 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 23:12:06 +0100 Subject: [PATCH 17/30] Doc: Write about bytes/text management This should eventually be moved elsewhere in the docs. --- Doc/index.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Doc/index.rst b/Doc/index.rst index 14992a6..1f9bf6a 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -34,6 +34,43 @@ Contents 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 ****************** From b0c908149af50bd3645744091ddf3697c4e02750 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 23:13:08 +0100 Subject: [PATCH 18/30] Modules: Use alternate sasl.h location on macOS --- Modules/LDAPObject.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 95387a0..43bde96 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -14,8 +14,12 @@ #include "options.h" #ifdef HAVE_SASL +#ifdef __APPLE__ +#include +#else #include #endif +#endif static void free_attrs(char***, PyObject*); From 2a30680d10ffc9db9adf634e7d075965a7ec1fcd Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 23:14:04 +0100 Subject: [PATCH 19/30] Modules: Eliminate getter & setter that reimplement the default --- Modules/LDAPObject.c | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 43bde96..2eff88e 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1384,23 +1384,6 @@ 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 = { @@ -1416,8 +1399,8 @@ PyTypeObject LDAP_Type = { /* 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*/ From 83763d908bb443f3284d72bf9f43487fb112c175 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 23:26:05 +0100 Subject: [PATCH 20/30] Tests: Add a test for whoami after unbind --- Tests/t_cext.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 3cbdbe7..cd171b6 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -576,6 +576,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 From 750fe8cd39ff49f5e21b5e3133b112ae23afec31 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:09:22 +0100 Subject: [PATCH 21/30] Modules: Use Python 3- compatible module initialization --- Modules/ldapmodule.c | 65 ++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index a788ae1..46c9c8a 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -8,6 +8,12 @@ #include "LDAPObject.h" +#if PY_MAJOR_VERSION >= 3 +PyMODINIT_FUNC PyInit__ldap(void); +#else +PyMODINIT_FUNC init_ldap(void); +#endif + #define _STR(x) #x #define STR(x) _STR(x) @@ -15,38 +21,24 @@ static char version_str[] = STR(LDAPMODULE_VERSION); static char author_str[] = STR(LDAPMODULE_AUTHOR); static char license_str[] = STR(LDAPMODULE_LICENSE); -void -LDAPinit_pkginfo( PyObject* d ) +static void +init_pkginfo( PyObject* m ) { - PyObject *version; - PyObject *author; - PyObject *license; - - version = PyString_FromString(version_str); - author = PyString_FromString(author_str); - license = PyString_FromString(license_str); - - PyDict_SetItemString( d, "__version__", version ); - PyDict_SetItemString(d, "__author__", author); - PyDict_SetItemString(d, "__license__", license); - - Py_DECREF(version); - Py_DECREF(author); - Py_DECREF(license); + PyModule_AddStringConstant(m, "__version__", version_str); + PyModule_AddStringConstant(m, "__author__", author_str); + PyModule_AddStringConstant(m, "__license__", license_str); } -DL_EXPORT(void) init_ldap(void); - /* 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; @@ -55,12 +47,26 @@ 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_pkginfo(d); + init_pkginfo(m); + LDAPinit_constants(d); LDAPinit_errors(d); LDAPinit_functions(d); @@ -69,4 +75,17 @@ init_ldap() /* 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 From fa35757fa2a425784676a91909b8f721cf13c095 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:45:54 +0100 Subject: [PATCH 22/30] py3: Make the bytes/text distinction - DNs, attribute names, URLs are text (encoded to UTF-8 on the wire) - Attribute values are always bytes A "bytes_mode" switch controls behavior under Python 2. --- Lib/ldap/dn.py | 3 + Lib/ldap/functions.py | 12 +- Lib/ldap/ldapobject.py | 214 +++++++++++++++++++++++++++++++++++- Lib/ldap/sasl.py | 6 + Lib/ldap/schema/models.py | 3 + Lib/ldap/schema/subentry.py | 4 +- Lib/ldif.py | 58 ++++++++-- Lib/slapdtest.py | 12 +- Modules/LDAPObject.c | 41 ++++--- Modules/berval.c | 24 +++- Modules/berval.h | 1 + Modules/constants.c | 34 +++--- Modules/errors.c | 8 +- Modules/functions.c | 6 +- Modules/ldapcontrol.c | 8 +- Modules/message.c | 43 ++++++-- Modules/options.c | 4 +- Tests/t_cext.py | 72 ++++++------ Tests/t_ldap_dn.py | 35 +++--- Tests/t_ldap_syncrepl.py | 32 +++--- Tests/t_ldapobject.py | 158 ++++++++++++++++++++++---- Tests/t_ldapurl.py | 6 +- Tests/t_ldif.py | 24 +++- 23 files changed, 636 insertions(+), 172 deletions(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 3298551..00c7b06 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -4,6 +4,7 @@ See https://www.python-ldap.org/ for details. """ +import sys from ldap.pkginfo import __version__ import _ldap @@ -46,6 +47,8 @@ def str2dn(dn,flags=0): """ if not dn: return [] + if sys.version_info[0] < 3 and isinstance(dn, unicode): + dn = dn.encode('utf-8') return ldap.functions._ldap_function_call(None,_ldap.str2dn,dn,flags) diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index c60d42b..b887037 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -62,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 @@ -76,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 @@ -95,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 e83287d..01f0044 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -4,6 +4,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + from os import strerror from ldap.pkginfo import __version__, __author__, __license__ @@ -20,6 +22,7 @@ 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 @@ -28,6 +31,11 @@ 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): """ @@ -55,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 @@ -66,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))) @@ -185,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): @@ -209,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): @@ -285,6 +476,7 @@ 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): @@ -315,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): @@ -363,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): @@ -416,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): @@ -437,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): @@ -525,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): @@ -572,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, @@ -665,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( @@ -686,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 @@ -788,7 +994,7 @@ class ReconnectLDAPObject(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, retry_max=1,retry_delay=60.0 ): """ @@ -803,7 +1009,7 @@ 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 diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index 34d4cb0..fa6f4f5 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -46,6 +46,8 @@ def __init__(self, cb_value_dict, mech): 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): @@ -64,6 +66,8 @@ def callback(self, cb_id, challenge, prompt, defresult): 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). + + Unicode strings are always converted to bytes. """ # The following print command might be useful for debugging @@ -78,6 +82,8 @@ def callback(self, cb_id, challenge, prompt, defresult): 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 diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 300981e..c0391b4 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -32,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: @@ -46,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]) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 4d42b19..2a42b4c 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -456,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) diff --git a/Lib/ldif.py b/Lib/ldif.py index 94ffd91..82c4e3f 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -4,6 +4,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + __version__ = '2.5.2' __all__ = [ @@ -60,7 +62,7 @@ def is_dn(s): return rm!=None and rm.group(0)==s -SAFE_STRING_PATTERN = '(^(\000|\n|\r| |:|<)|[\000\n\r\200-\377]+|[ ]+$)' +SAFE_STRING_PATTERN = b'(^(\000|\n|\r| |:|<)|[\000\n\r\200-\377]+|[ ]+$)' safe_string_re = re.compile(SAFE_STRING_PATTERN) def list_dict(l): @@ -80,7 +82,7 @@ class LDIFWriter: def __init__(self,output_file,base64_attrs=None,cols=76,line_sep='\n'): """ output_file - file object for output + file object for output; should be opened in *text* mode base64_attrs list of attribute types to be base64-encoded in any case cols @@ -129,15 +131,17 @@ def _unparseAttrTypeandValue(self,attr_type,attr_value): Write a single attribute type/value pair attr_type - attribute type + attribute type (text) attr_value - attribute value + attribute value (bytes) """ if self._needs_base64_encoding(attr_type,attr_value): # Encode with base64 - self._unfold_lines(':: '.join([attr_type, b64encode(attr_value).replace('\n','')])) + encoded = b64encode(attr_value).decode('ascii') + encoded = encoded.replace('\n','') + self._unfold_lines(':: '.join([attr_type, encoded])) else: - self._unfold_lines(': '.join([attr_type,attr_value])) + self._unfold_lines(': '.join([attr_type, attr_value.decode('ascii')])) return # _unparseAttrTypeandValue() def _unparseEntryRecord(self,entry): @@ -161,13 +165,14 @@ def _unparseChangeRecord(self,modlist): changetype = 'modify' else: raise ValueError("modlist item of wrong length: %d" % (mod_len)) - self._unparseAttrTypeandValue('changetype',changetype) + self._unparseAttrTypeandValue('changetype',changetype.encode('ascii')) for mod in modlist: if mod_len==2: mod_type,mod_vals = mod elif mod_len==3: mod_op,mod_type,mod_vals = mod - self._unparseAttrTypeandValue(MOD_OP_STR[mod_op],mod_type) + self._unparseAttrTypeandValue(MOD_OP_STR[mod_op], + mod_type.encode('ascii')) else: raise ValueError("Subsequent modlist item of wrong length") if mod_vals: @@ -185,7 +190,8 @@ def unparse(self,dn,record): or a list with a modify list like for LDAPObject.modify(). """ # Start with line containing the distinguished name - self._unparseAttrTypeandValue('dn',dn) + dn = dn.encode('utf-8') + self._unparseAttrTypeandValue('dn', dn) # Dispatch to record type specific writers if isinstance(record,dict): self._unparseEntryRecord(record) @@ -260,6 +266,8 @@ def __init__( String used as line separator """ self._input_file = input_file + # Detect whether the file is open in text or bytes mode. + self._file_sends_bytes = isinstance(self._input_file.read(0), bytes) self._max_entries = max_entries self._process_url_schemes = list_dict([s.lower() for s in (process_url_schemes or [])]) self._ignored_attr_types = list_dict([a.lower() for a in (ignored_attr_types or [])]) @@ -287,6 +295,10 @@ def handle(self,dn,entry): def _readline(self): s = self._input_file.readline() + if self._file_sends_bytes: + # The RFC does not allow UTF-8 values; we support it as a + # non-official, backwards compatibility layer + s = s.decode('utf-8') self.line_counter = self.line_counter + 1 self.byte_counter = self.byte_counter + len(s) if not s: @@ -319,6 +331,8 @@ def _next_key_and_value(self): """ Parse a single attribute type and value pair from one or more lines of LDIF data + + Returns attr_type (text) and attr_value (bytes) """ # Reading new attribute line unfolded_line = self._unfold_lines() @@ -338,9 +352,15 @@ def _next_key_and_value(self): value_spec = unfolded_line[colon_pos:colon_pos+2] if value_spec==': ': attr_value = unfolded_line[colon_pos+2:].lstrip() + # All values should be valid ascii; we support UTF-8 as a + # non-official, backwards compatibility layer. + attr_value = attr_value.encode('utf-8') elif value_spec=='::': # attribute value needs base64-decoding - attr_value = self._b64decode(unfolded_line[colon_pos+2:]) + # base64 makes sens only for ascii + attr_value = unfolded_line[colon_pos+2:] + attr_value = attr_value.encode('ascii') + attr_value = self._b64decode(attr_value) elif value_spec==':<': # fetch attribute value from URL url = unfolded_line[colon_pos+2:].strip() @@ -350,7 +370,9 @@ def _next_key_and_value(self): if u[0] in self._process_url_schemes: attr_value = urlopen(url).read() else: - attr_value = unfolded_line[colon_pos+1:] + # All values should be valid ascii; we support UTF-8 as a + # non-official, backwards compatibility layer. + attr_value = unfolded_line[colon_pos+1:].encode('utf-8') return attr_type,attr_value def _consume_empty_lines(self): @@ -383,7 +405,7 @@ def parse_entry_records(self): k,v = self._consume_empty_lines() # Consume 'version' line if k=='version': - self.version = int(v) + self.version = int(v.decode('ascii')) k,v = self._consume_empty_lines() except EOFError: return @@ -394,6 +416,9 @@ def parse_entry_records(self): # Consume first line which must start with "dn: " if k!='dn': raise ValueError('Line %d: First line of record does not start with "dn:": %s' % (self.line_counter,repr(k))) + # Value of a 'dn' field *has* to be valid UTF-8 + # k is text, v is bytes. + v = v.decode('utf-8') if not is_dn(v): raise ValueError('Line %d: Not a valid string-representation for dn: %s.' % (self.line_counter,repr(v))) dn = v @@ -452,6 +477,9 @@ def parse_change_records(self): # Consume first line which must start with "dn: " if k!='dn': raise ValueError('Line %d: First line of record does not start with "dn:": %s' % (self.line_counter,repr(k))) + # Value of a 'dn' field *has* to be valid UTF-8 + # k is text, v is bytes. + v = v.decode('utf-8') if not is_dn(v): raise ValueError('Line %d: Not a valid string-representation for dn: %s.' % (self.line_counter,repr(v))) dn = v @@ -460,6 +488,8 @@ def parse_change_records(self): # Read "control:" lines controls = [] while k!=None and k=='control': + # v is still bytes, spec says it should be valid utf-8; decode it. + v = v.decode('utf-8') try: control_type,criticality,control_value = v.split(' ',2) except ValueError: @@ -472,6 +502,8 @@ def parse_change_records(self): changetype = None # Consume changetype line of record if k=='changetype': + # v is still bytes, spec says it should be valid utf-8; decode it. + v = v.decode('utf-8') if not v in valid_changetype_dict: raise ValueError('Invalid changetype: %s' % repr(v)) changetype = v @@ -491,6 +523,8 @@ def parse_change_records(self): except KeyError: raise ValueError('Line %d: Invalid mod-op string: %s' % (self.line_counter,repr(k))) # we now have the attribute name to be modified + # v is still bytes, spec says it should be valid utf-8; decode it. + v = v.decode('utf-8') modattr = v modvalues = [] try: diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 1aba887..fcd36e1 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + __version__ = '2.5.2' import os @@ -365,13 +367,15 @@ def ldapadd(self, ldif, extra_args=None): """ Runs ldapadd on this slapd instance, passing it the ldif content """ - self._cli_popen(self.PATH_LDAPADD, extra_args=extra_args, stdin_data=ldif) + self._cli_popen(self.PATH_LDAPADD, extra_args=extra_args, + stdin_data=ldif.encode('utf-8')) def ldapmodify(self, ldif, extra_args=None): """ Runs ldapadd on this slapd instance, passing it the ldif content """ - self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args, stdin_data=ldif) + self._cli_popen(self.PATH_LDAPMODIFY, extra_args=extra_args, + stdin_data=ldif.encode('utf-8')) class SlapdTestCase(unittest.TestCase): @@ -383,11 +387,11 @@ class SlapdTestCase(unittest.TestCase): server = None ldap_object_class = None - def _open_ldap_conn(self, who=None, cred=None): + def _open_ldap_conn(self, who=None, cred=None, **kwargs): """ return a LDAPObject instance after simple bind """ - ldap_conn = self.ldap_object_class(self.server.ldap_uri) + ldap_conn = self.ldap_object_class(self.server.ldap_uri, **kwargs) ldap_conn.protocol_version = 3 #ldap_conn.set_option(ldap.OPT_REFERRALS, 0) ldap_conn.simple_bind_s(who or self.server.root_dn, cred or self.server.root_pw) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 2eff88e..ce0ff52 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -142,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) @@ -151,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); @@ -166,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) @@ -268,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 )); @@ -289,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; } @@ -551,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 @@ -647,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 ); @@ -699,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; @@ -1188,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; } diff --git a/Modules/berval.c b/Modules/berval.c index b118669..73d7f9b 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 514e9f9..2489e45 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/constants.c b/Modules/constants.c index 06c249a..7ed9e41 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -307,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 3731965..e5cb0ee 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 b2dbf83..ffb6765 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 0bf86a1..f3dc92a 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/message.c b/Modules/message.c index 33127b6..1a289db 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/options.c b/Modules/options.c index a437ca4..7cf996b 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/Tests/t_cext.py b/Tests/t_cext.py index cd171b6..d4740d0 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 @@ -195,7 +197,7 @@ def test_anon_rootdse_search(self): '', _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, []) @@ -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): @@ -590,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)) diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 97274f3..459c1dc 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,9 +29,10 @@ 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 ) @@ -97,9 +100,9 @@ def test_str2dn(self): ] ) self.assertEqual( - ldap.dn.str2dn('cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f,dc=example,dc=com', flags=0), + ldap.dn.str2dn('cn=äöüÄÖÜß,dc=example,dc=com', flags=0), [ - [('cn', '\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 4)], + [('cn', 'äöüÄÖÜß', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)] ] @@ -107,7 +110,7 @@ def test_str2dn(self): 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', '\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 4)], + [('cn', 'äöüÄÖÜß', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)] ] @@ -156,19 +159,11 @@ def test_dn2str(self): ) self.assertEqual( ldap.dn.dn2str([ - [('cn', '\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 4)], - [('dc', 'example', 1)], - [('dc', 'com', 1)] - ]), - 'cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f,dc=example,dc=com' - ) - self.assertEqual( - ldap.dn.dn2str([ - [('cn', '\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 4)], + [('cn', 'äöüÄÖÜß', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)] ]), - 'cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f,dc=example,dc=com' + 'cn=äöüÄÖÜß,dc=example,dc=com' ) def test_explode_dn(self): @@ -197,12 +192,12 @@ def test_explode_dn(self): ['uid=test\\, 42', 'ou=Testing', 'dc=example', 'dc=com'] ) self.assertEqual( - ldap.dn.explode_dn('cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f,dc=example,dc=com', flags=0), - ['cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 'dc=example', 'dc=com'] + 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=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 'dc=example', 'dc=com'] + ['cn=äöüÄÖÜß', 'dc=example', 'dc=com'] ) def test_explode_rdn(self): @@ -239,12 +234,12 @@ def test_explode_rdn(self): ['uid=test\\+ 42'] ) self.assertEqual( - ldap.dn.explode_rdn('cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', flags=0), - ['cn=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'] + 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=\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f'] + ['cn=äöüÄÖÜß'] ) diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 8f39c67..831c063 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -81,34 +81,34 @@ # 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': ['organizationalUnit'], - 'ou': ['Container'] + 'objectClass': [b'organizationalUnit'], + 'ou': [b'Container'] }, 'cn=Foo2,dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['organizationalRole'], - 'cn': ['Foo2'] + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo2'] }, 'cn=Foo4,ou=Container,dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['organizationalRole'], - 'cn': ['Foo4'] + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo4'] }, 'cn=Manager,dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['applicationProcess', 'simpleSecurityObject'], - 'userPassword': ['password'], - 'cn': ['Manager'] + 'objectClass': [b'applicationProcess', b'simpleSecurityObject'], + 'userPassword': [b'password'], + 'cn': [b'Manager'] }, 'cn=Foo3,dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['organizationalRole'], - 'cn': ['Foo3'] + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo3'] }, 'cn=Foo1,dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['organizationalRole'], - 'cn': ['Foo1'] + 'objectClass': [b'organizationalRole'], + 'cn': [b'Foo1'] }, 'dc=slapd-test,dc=python-ldap,dc=org': { - 'objectClass': ['dcObject', 'organization'], - 'dc': ['slapd-test'], - 'o': ['slapd-test'] + 'objectClass': [b'dcObject', b'organization'], + 'dc': [b'slapd-test'], + 'o': [b'slapd-test'] } } diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 5773ff2..d55d018 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -5,6 +5,17 @@ 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 @@ -83,7 +94,101 @@ 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_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( @@ -98,19 +203,19 @@ def test001_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']} ), ] ) @@ -128,15 +233,15 @@ def test002_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']} ), ] ) @@ -151,9 +256,22 @@ def test003_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_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: @@ -228,20 +346,22 @@ def test103_reconnect_get_state(self): self.assertEqual( l1.__getstate__(), { - '_last_bind': ( + str('_last_bind'): ( 'simple_bind_s', (bind_dn, 'user1_pw'), {} ), - '_options': [(17, 3)], - '_reconnects_done': 0L, - '_retry_delay': 60.0, - '_retry_max': 1, - '_start_tls': 0, - '_trace_level': 0, - '_trace_stack_limit': 5, - '_uri': self.server.ldapi_uri, - 'timeout': -1, + 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, }, ) diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index a68b032..2be03f6 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + import unittest from ldap.compat import quote @@ -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 76701cc..adf0d26 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', { @@ -273,6 +275,26 @@ def test_big_binary(self): ) 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 From 388eafbf7d1d105385704b3f84ae76e15d8e788c Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:46:12 +0100 Subject: [PATCH 23/30] Modules: Python 3, alias PyInt to PyLong --- Modules/common.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Modules/common.h b/Modules/common.h index 1ec232c..0eea1d9 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -27,5 +27,11 @@ 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_ */ From 4549352213556c4cd3303f2ae0fab9a90abf5bc1 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:46:50 +0100 Subject: [PATCH 24/30] Tests: Add a test suite for binds --- Tests/__init__.py | 1 + Tests/t_bind.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 Tests/t_bind.py diff --git a/Tests/__init__.py b/Tests/__init__.py index df95a06..1ca8d20 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -7,6 +7,7 @@ from __future__ import absolute_import +from . import t_bind from . import t_cext from . import t_cidict from . import t_ldap_dn diff --git a/Tests/t_bind.py b/Tests/t_bind.py new file mode 100644 index 0000000..373688b --- /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() From 2949e780efe25ae65f9e893a952d3e85abf73972 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:47:14 +0100 Subject: [PATCH 25/30] Tests: Add a test suite for edits --- Tests/__init__.py | 1 + Tests/t_edit.py | 89 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 Tests/t_edit.py diff --git a/Tests/__init__.py b/Tests/__init__.py index 1ca8d20..46b3861 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -18,4 +18,5 @@ 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_edit.py b/Tests/t_edit.py new file mode 100644 index 0000000..9aee43e --- /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() From ebcdaae35bb126dd94bafd5d2d82f1460c1562b0 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:48:30 +0100 Subject: [PATCH 26/30] Tests: Add a smoke-check for listall() and attribute_types() --- Tests/t_ldap_schema_subentry.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index de6421a..83ed006 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() From eb06c59afa2ca0c440db8f9e9c2181f3931b4c17 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:51:29 +0100 Subject: [PATCH 27/30] Infra: Advertise and test Python 3 compatibility - Add 3.3 - 3.6 to tox.ini - Add .travis.yml for Travis CI - Add 3.3 - 3.6 to Trove classifiers - Add Python version checking to setup.py --- .travis.yml | 25 +++++++++++++++++++++++++ setup.py | 25 ++++++++++++++++++++++--- tox.ini | 3 ++- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..542377d --- /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/setup.py b/setup.py index a475a35..af6443b 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,8 +13,15 @@ except ImportError: from distutils.core import setup, Extension -from ConfigParser import ConfigParser -import sys,os,time +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 @@ -59,7 +68,8 @@ 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( @@ -88,8 +98,17 @@ 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', diff --git a/tox.ini b/tox.ini index d11ce1c..61a6aed 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 From 9608ead48b756b514f8a1e65135cd9b091d78683 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:53:46 +0100 Subject: [PATCH 28/30] Infra: Set authorship to the python-ldap project --- setup.cfg | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 10dafc9..59816b7 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 af6443b..89351b5 100644 --- a/setup.py +++ b/setup.py @@ -85,7 +85,7 @@ class OpenLDAP2: (e.g. processing LDIF, LDAPURLs, LDAPv3 schema, LDAPv3 extended operations and controls, etc.). """, - author = pkginfo.__author__, + author = 'python-ldap project', author_email = 'python-ldap@python.org', url = 'https://www.python-ldap.org/', download_url = 'https://pypi.python.org/pypi/python-ldap/', From 92283a24a1a7dcdd60ac9368b6a5e71c235f9d89 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 11:26:23 +0100 Subject: [PATCH 29/30] Infra: Thank all pyldap contributors The changes are squashed, so we don't have line-by-line authorship in Git. List people here instead, and link to the history. --- README | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README b/README index 6081b4b..431559d 100644 --- a/README +++ b/README @@ -100,6 +100,27 @@ These very kind people have supplied patches or suggested changes: * 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. From 31389576dcf1badff9c8cbe4af445bbb9f2ef69d Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 24 Nov 2017 11:41:55 +0100 Subject: [PATCH 30/30] Infra: Update CHANGELOG --- CHANGES | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a8ad44a..113eb27 100644 --- a/CHANGES +++ b/CHANGES @@ -4,11 +4,15 @@ Released 3.0.0 xxxx-xx-xx Changes since 2.4.45: Mandatory prerequisites: -- Python 2.7.x +- 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) @@ -48,6 +52,10 @@ Lib/ * 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 @@ -56,6 +64,13 @@ Tests/ * 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