From e9aeb1107328ca450f3b62d1377a14ab58ecd25e Mon Sep 17 00:00:00 2001 From: stroeder Date: Tue, 7 Nov 2017 21:08:45 +0000 Subject: [PATCH 001/121] started 2.4.46 --- CHANGES | 11 +++++++++++ Lib/dsml.py | 2 +- Lib/ldap/__init__.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest.py | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 4d879e42..29060060 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,14 @@ +---------------------------------------------------------------- +Released 2.4.46 2017-11-xx + +Changes since 2.4.45: + +Lib/ +* + +Tests/ +* + ---------------------------------------------------------------- Released 2.4.45 2017-10-09 diff --git a/Lib/dsml.py b/Lib/dsml.py index 9a93b948..56b7dd38 100644 --- a/Lib/dsml.py +++ b/Lib/dsml.py @@ -8,7 +8,7 @@ Tested with Python 2.0+. """ -__version__ = '2.4.45' +__version__ = '2.4.46' import string,base64 diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 30c0010e..95e408ae 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -6,7 +6,7 @@ # This is also the overall release version number -__version__ = '2.4.45' +__version__ = '2.4.46' import sys diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index c760b33a..0432f925 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -9,7 +9,7 @@ 2. list comprehensions are used. """ -__version__ = '2.4.45' +__version__ = '2.4.46' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index bd5801c7..51bb3bd7 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -7,7 +7,7 @@ Tested with Python 2.0+, but should work with Python 1.5.2+. """ -__version__ = '2.4.45' +__version__ = '2.4.46' __all__ = [ # constants diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index a09e9e60..83460404 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -8,7 +8,7 @@ This module only works with Python 2.7.x since """ -__version__ = '2.4.45' +__version__ = '2.4.46' import os import socket From 0829d5230ee148aa266fbc3d5e0d816a11ae176a Mon Sep 17 00:00:00 2001 From: stroeder Date: Tue, 7 Nov 2017 21:10:52 +0000 Subject: [PATCH 002/121] slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data --- CHANGES | 3 ++- Lib/slapdtest.py | 14 ++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index 29060060..f687fcd3 100644 --- a/CHANGES +++ b/CHANGES @@ -4,7 +4,8 @@ Released 2.4.46 2017-11-xx Changes since 2.4.45: Lib/ -* +* slapdtest.SlapdObject.restart() just restarts slapd + without cleaning any data Tests/ * diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 83460404..5c4a83f9 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -285,7 +285,9 @@ def start(self): ) def stop(self): - """Stops the slapd server, and waits for it to terminate""" + """ + Stops the slapd server, and waits for it to terminate and cleans up + """ if self._proc is not None: self._log.debug('stopping slapd with pid %d', self._proc.pid) self._proc.terminate() @@ -294,11 +296,11 @@ def stop(self): def restart(self): """ - Restarts the slapd server; ERASING previous content. - Starts the server even it if isn't already running. + Restarts the slapd server with same data """ - self.stop() - self.start() + self._proc.terminate() + self.wait() + self._start_slapd() def wait(self): """Waits for the slapd process to terminate by itself.""" @@ -309,7 +311,7 @@ def wait(self): def _stopped(self): """Called when the slapd server is known to have terminated""" if self._proc is not None: - self._log.info('slapd terminated') + self._log.info('slapd[%d] terminated', self._proc.pid) self._proc = None def _cli_auth_args(self): From d534a46ba5a1690c5dd35fd5bfbd3cf123d35e3e Mon Sep 17 00:00:00 2001 From: stroeder Date: Tue, 7 Nov 2017 21:12:25 +0000 Subject: [PATCH 003/121] added explicit reconnect tests for ReconnectLDAPObject --- CHANGES | 2 +- Tests/t_ldapobject.py | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index f687fcd3..8e332074 100644 --- a/CHANGES +++ b/CHANGES @@ -8,7 +8,7 @@ Lib/ without cleaning any data Tests/ -* +* added explicit reconnect tests for ReconnectLDAPObject ---------------------------------------------------------------- Released 2.4.45 2017-10-09 diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 60e9bdb7..b7836777 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -27,6 +27,12 @@ cn: %(rootcn)s userPassword: %(rootpw)s +dn: cn=user1,%(suffix)s +objectClass: applicationProcess +objectClass: simpleSecurityObject +cn: user1 +userPassword: user1_pw + dn: cn=Foo1,%(suffix)s objectClass: organizationalRole cn: Foo1 @@ -190,6 +196,22 @@ class Test02_ReconnectLDAPObject(Test01_SimpleLDAPObject): ldap_object_class = ReconnectLDAPObject + def test_reconnect_sasl_external(self): + l = self.ldap_object_class(self.server.ldapi_uri) + l.sasl_external_bind_s() + authz_id = l.whoami_s() + self.assertEqual(authz_id, 'dn:'+self.server.root_dn.lower()) + self.server.restart() + self.assertEqual(l.whoami_s(), authz_id) + + def test_reconnect_simple_bind(self): + l = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) + self.server.restart() + self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) + if __name__ == '__main__': unittest.main() From cd3d83bc1caf3b47c65e56511949d1f07677ce7f Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 11 Nov 2017 19:32:14 +0000 Subject: [PATCH 004/121] started 2.5.0 (instead of 2.4.46) --- CHANGES | 2 +- Lib/dsml.py | 2 +- Lib/ldap/__init__.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 8e332074..c4d02831 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ ---------------------------------------------------------------- -Released 2.4.46 2017-11-xx +Released 2.5.0 2017-11-xx Changes since 2.4.45: diff --git a/Lib/dsml.py b/Lib/dsml.py index 56b7dd38..bf58c36e 100644 --- a/Lib/dsml.py +++ b/Lib/dsml.py @@ -8,7 +8,7 @@ Tested with Python 2.0+. """ -__version__ = '2.4.46' +__version__ = '2.5.0' import string,base64 diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 95e408ae..af83b6c5 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -6,7 +6,7 @@ # This is also the overall release version number -__version__ = '2.4.46' +__version__ = '2.5.0' import sys diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 0432f925..07a6863d 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -9,7 +9,7 @@ 2. list comprehensions are used. """ -__version__ = '2.4.46' +__version__ = '2.5.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 51bb3bd7..2de56504 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -7,7 +7,7 @@ Tested with Python 2.0+, but should work with Python 1.5.2+. """ -__version__ = '2.4.46' +__version__ = '2.5.0' __all__ = [ # constants diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 5c4a83f9..d5e6a828 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -8,7 +8,7 @@ This module only works with Python 2.7.x since """ -__version__ = '2.4.46' +__version__ = '2.5.0' import os import socket From dab08dffcadd6b16cfa0e37a9cf08bc0aeb51419 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 11 Nov 2017 20:24:34 +0000 Subject: [PATCH 005/121] Compability changes for pyasn1 0.3.x or newer --- CHANGES | 4 ++++ Lib/ldap/controls/ppolicy.py | 12 ++++++------ Lib/ldap/controls/psearch.py | 20 +++++++++----------- Lib/ldap/controls/sss.py | 6 +++++- Lib/ldap/controls/vlv.py | 12 +++++++----- Lib/ldap/syncrepl.py | 28 +++++++++++++++++----------- 6 files changed, 48 insertions(+), 34 deletions(-) diff --git a/CHANGES b/CHANGES index c4d02831..960919e8 100644 --- a/CHANGES +++ b/CHANGES @@ -3,9 +3,13 @@ Released 2.5.0 2017-11-xx Changes since 2.4.45: +This release requires pyasn1 0.3.x or newer! + Lib/ * slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data +* Compability changes for pyasn1 0.3.x or newer + (thanks to Ilya Etingof and Christian Heimes) Tests/ * added explicit reconnect tests for ReconnectLDAPObject diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index a0fa1762..aa761f34 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -71,24 +71,24 @@ def __init__(self,criticality=False): def decodeControlValue(self,encodedControlValue): ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) warning = ppolicyValue.getComponentByName('warning') - if warning is None: + if not warning.hasValue(): self.timeBeforeExpiration,self.graceAuthNsRemaining = None,None else: timeBeforeExpiration = warning.getComponentByName('timeBeforeExpiration') - if timeBeforeExpiration!=None: + if timeBeforeExpiration.hasValue(): self.timeBeforeExpiration = int(timeBeforeExpiration) else: self.timeBeforeExpiration = None graceAuthNsRemaining = warning.getComponentByName('graceAuthNsRemaining') - if graceAuthNsRemaining!=None: + if graceAuthNsRemaining.hasValue(): self.graceAuthNsRemaining = int(graceAuthNsRemaining) else: self.graceAuthNsRemaining = None error = ppolicyValue.getComponentByName('error') - if error is None: - self.error = None - else: + if error.hasValue(): self.error = int(error) + else: + self.error = None KNOWN_RESPONSE_CONTROLS[PasswordPolicyControl.controlType] = PasswordPolicyControl diff --git a/Lib/ldap/controls/psearch.py b/Lib/ldap/controls/psearch.py index e639ed8a..91a5c241 100644 --- a/Lib/ldap/controls/psearch.py +++ b/Lib/ldap/controls/psearch.py @@ -115,18 +115,16 @@ class EntryChangeNotificationControl(ResponseControl): def decodeControlValue(self,encodedControlValue): ecncValue,_ = decoder.decode(encodedControlValue,asn1Spec=EntryChangeNotificationValue()) self.changeType = int(ecncValue.getComponentByName('changeType')) - if len(ecncValue)==3: - self.previousDN = str(ecncValue.getComponentByName('previousDN')) - self.changeNumber = int(ecncValue.getComponentByName('changeNumber')) - elif len(ecncValue)==2: - if self.changeType==8: - self.previousDN = str(ecncValue.getComponentByName('previousDN')) - self.changeNumber = None - else: - self.previousDN = None - self.changeNumber = int(ecncValue.getComponentByName('changeNumber')) + previousDN = ecncValue.getComponentByName('previousDN') + if previousDN.hasValue(): + self.previousDN = str(previousDN) else: - self.previousDN,self.changeNumber = None,None + self.previousDN = None + changeNumber = ecncValue.getComponentByName('changeNumber') + if changeNumber.hasValue(): + self.changeNumber = int(changeNumber) + else: + self.changeNumber = None return (self.changeType,self.previousDN,self.changeNumber) KNOWN_RESPONSE_CONTROLS[EntryChangeNotificationControl.controlType] = EntryChangeNotificationControl diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index bf2773b9..20697faf 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -121,7 +121,11 @@ def decodeControlValue(self, encoded): assert not rest, 'all data could not be decoded' self.result = int(p.getComponentByName('sortResult')) self.result_code = p.getComponentByName('sortResult').prettyOut(self.result) - self.attribute_type_error = p.getComponentByName('attributeType') + attribute_type_error = p.getComponentByName('attributeType') + if attribute_type_error.hasValue(): + self.attribute_type_error = attribute_type_error + else: + self.attribute_type_error = None KNOWN_RESPONSE_CONTROLS[SSSRequestControl.controlType] = SSSRequestControl diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index d5b0f899..980b77ba 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -128,10 +128,12 @@ def decodeControlValue(self,encoded): self.target_position = int(p.getComponentByName('targetPosition')) self.content_count = int(p.getComponentByName('contentCount')) self.result = int(p.getComponentByName('virtualListViewResult')) - self.result_code = p.getComponentByName('virtualListViewResult') \ - .prettyOut(self.result) - self.context_id = p.getComponentByName('contextID') - if self.context_id: - self.context_id = str(self.context_id) + self.result_code = p.getComponentByName('virtualListViewResult').prettyOut(self.result) + context_id = p.getComponentByName('contextID') + if context_id.hasValue(): + self.context_id = str(context_id) + else: + self.context_id = None + KNOWN_RESPONSE_CONTROLS[VLVResponseControl.controlType] = VLVResponseControl diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 3401c0df..94a7217e 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -131,11 +131,13 @@ def decodeControlValue(self, encodedControlValue): d = decoder.decode(encodedControlValue, asn1Spec = syncStateValue()) state = d[0].getComponentByName('state') uuid = UUID(bytes=d[0].getComponentByName('entryUUID')) - self.cookie = d[0].getComponentByName('cookie') + cookie = d[0].getComponentByName('cookie') + if cookie.hasValue(): + self.cookie = str(self.cookie) + else: + self.cookie = None self.state = self.__class__.opnames[int(state)] self.entryUUID = str(uuid) - if self.cookie is not None: - self.cookie = str(self.cookie) KNOWN_RESPONSE_CONTROLS[SyncStateControl.controlType] = SyncStateControl @@ -165,12 +167,16 @@ class SyncDoneControl(ResponseControl): def decodeControlValue(self, encodedControlValue): d = decoder.decode(encodedControlValue, asn1Spec = syncDoneValue()) - self.cookie = d[0].getComponentByName('cookie') - self.refreshDeletes = d[0].getComponentByName('refreshDeletes') - if self.cookie is not None: - self.cookie = str(self.cookie) - if self.refreshDeletes is not None: - self.refreshDeletes = bool(self.refreshDeletes) + cookie = d[0].getComponentByName('cookie') + if cookie.hasValue(): + self.cookie = str(cookie) + else: + self.cookie = None + refresh_deletes = d[0].getComponentByName('refreshDeletes') + if refresh_deletes.hasValue(): + self.refreshDeletes = bool(refresh_deletes) + else: + self.refreshDeletes = None KNOWN_RESPONSE_CONTROLS[SyncDoneControl.controlType] = SyncDoneControl @@ -263,7 +269,7 @@ def __init__(self, encodedMessage): for attr in [ 'newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']: comp = d[0].getComponentByName(attr) - if comp is not None: + if comp.hasValue(): if attr == 'newcookie': self.newcookie = str(comp) @@ -272,7 +278,7 @@ def __init__(self, encodedMessage): val = dict() cookie = comp.getComponentByName('cookie') - if cookie is not None: + if cookie.hasValue(): val['cookie'] = str(cookie) if attr.startswith('refresh'): From 5bb74dbb9dd173d7a9620897c07a8d50bddc89d6 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 11 Nov 2017 23:39:13 +0000 Subject: [PATCH 006/121] announce pyasn1* modules as Installation prerequisites, do not index C lib names --- Doc/installing.rst | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 116ba6fe..bcc41be3 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -15,8 +15,8 @@ use the same one you plan to use python-ldap with. See further instructions for using DistUtils here: https://docs.python.org/2.7/install/index.html -Prerequisites -============= +Build prerequisites +=================== The following software packages are required to be installed on the local system when building python-ldap: @@ -28,11 +28,18 @@ on the local system when building python-ldap: - cyrus-sasl (optional): https://www.cyrusimap.org/sasl/ - Kerberos libs, MIT or heimdal (optional) -Furthermore some sub-modules of :py:mod:`ldap.controls` and :py:mod:`ldap.extop` -require :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. +Installation prerequisites +========================== + +Furthermore it is required that modules +:py:mod:`pyasn1` and :py:mod:`pyasn1_modules` +are installed. https://github.com/etingof/pyasn1 + https://pypi.python.org/pypi/pyasn1 + + https://pypi.python.org/pypi/pyasn1-modules setup.cfg ========= @@ -66,25 +73,25 @@ documentation of Python's DistUtils. Libs used --------- -.. data:: ldap -.. data:: ldap_r +.. data:noindex: ldap +.. data:noindex: ldap_r The LDAP protocol library of OpenLDAP. ldap_r is the reentrant version and should be preferred. -.. data:: lber +.. data:noindex: lber The BER encoder/decoder library of OpenLDAP. -.. data:: sasl2 +.. data:noindex: sasl2 The Cyrus-SASL library if needed and present during build -.. data:: ssl +.. data:noindex: ssl The SSL/TLS library of OpenSSL if needed and present during build -.. data:: crypto +.. data:noindex: crypto The basic cryptographic library of OpenSSL if needed and present during build From 2ffd389aaa2fed3430eb935368f90fef7443f5a7 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 10:35:57 +0000 Subject: [PATCH 007/121] bumped Doc/ version to 2.5 --- Doc/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index c52b01d6..4e603c40 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -40,9 +40,9 @@ # other places throughout the built documents. # # The short X.Y version. -version = '2.4' +version = '2.5' # The full version, including alpha/beta/rc tags. -release = '2.4.45.0' +release = '2.5.0.0' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From c0485fb13fee2367404092ba92d6c8377f6cdbcc Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 12:57:47 +0000 Subject: [PATCH 008/121] added requirements.txt, pyasn1>=0.3.7 and pyasn1_modules>=0.1.5 now mandatory for python-ldap 2.5.0+ --- CHANGES | 4 +++- requirements.txt | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 requirements.txt diff --git a/CHANGES b/CHANGES index 960919e8..53fab22b 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,9 @@ Released 2.5.0 2017-11-xx Changes since 2.4.45: -This release requires pyasn1 0.3.x or newer! +This release now strictly requires +pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ +as installation prerequisites. Lib/ * slapdtest.SlapdObject.restart() just restarts slapd diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..465a037f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pyasn1>=0.3.7 +pyasn1_modules>=0.1.5 + From 5913883c3b9b43be2239d6906e64aa2dc6aa6d93 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:03:07 +0000 Subject: [PATCH 009/121] removed stand-alone module dsml --- Doc/index.rst | 1 - Lib/ldap/async.py | 28 ---------------------------- setup.py | 1 - 3 files changed, 30 deletions(-) diff --git a/Doc/index.rst b/Doc/index.rst index b93e9c1d..14992a6e 100644 --- a/Doc/index.rst +++ b/Doc/index.rst @@ -31,7 +31,6 @@ Contents ldap-sasl.rst ldif.rst ldapurl.rst - dsml.rst slapdtest.rst diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 2cbb4888..81824e47 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -285,31 +285,3 @@ def _processSingleResult(self,resultType,resultItem): # Search continuations are ignored dn,entry = resultItem self._ldif_writer.unparse(dn,entry) - - -class DSMLWriter(FileWriter): - """ - Class for writing a stream LDAP search results to a DSML file - - Arguments: - - l - LDAPObject instance - writer_obj - Either a file-like object or a dsml.DSMLWriter instance used for output - """ - - def __init__(self,l,writer_obj,headerStr='',footerStr=''): - import dsml - if isinstance(writer_obj,dsml.DSMLWriter): - self._dsml_writer = writer_obj - else: - self._dsml_writer = dsml.DSMLWriter(writer_obj) - FileWriter.__init__(self,l,self._dsml_writer._output_file,headerStr,footerStr) - - def _processSingleResult(self,resultType,resultItem): - if _entryResultTypes.has_key(resultType): - # Search continuations are ignored - dn,entry = resultItem - self._dsml_writer.unparse(dn,entry) - diff --git a/setup.py b/setup.py index 7318c41b..b75b7d17 100644 --- a/setup.py +++ b/setup.py @@ -145,7 +145,6 @@ class OpenLDAP2: py_modules = [ 'ldapurl', 'ldif', - 'dsml', 'ldap', 'slapdtest', 'ldap.async', From 9de7162d78b488820a9bcffa8b5747437c3865e4 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:20:23 +0000 Subject: [PATCH 010/121] module dsml was removed => docs obsolete --- Doc/dsml.rst | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 Doc/dsml.rst diff --git a/Doc/dsml.rst b/Doc/dsml.rst deleted file mode 100644 index 8b9a4601..00000000 --- a/Doc/dsml.rst +++ /dev/null @@ -1,26 +0,0 @@ -*************************************** -:mod:`dsml` DSMLv1 parser and generator -*************************************** - -.. :py:module:: dsml - :synopsis: Parses and generates DSMLv1 files -.. moduleauthor:: python-ldap project (see https://www.python-ldap.org/) - - -This module parses and generates LDAP data in the format DSMLv1. It is -implemented in pure Python and does not rely on any non-standard modules. -Therefore it can be used stand-alone without the rest of the python-ldap -package. - - -.. autoclass:: dsml.DSMLWriter - :members: - -.. autoclass:: dsml.DSMLParser - :members: - - -.. _dsml-example: - -Example -======= From b8d12d63fade159dfd3becc25929c8604976076e Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:21:06 +0000 Subject: [PATCH 011/121] remove stand-alone module dsml --- Lib/dsml.py | 295 ---------------------------------------------------- 1 file changed, 295 deletions(-) delete mode 100644 Lib/dsml.py diff --git a/Lib/dsml.py b/Lib/dsml.py deleted file mode 100644 index bf58c36e..00000000 --- a/Lib/dsml.py +++ /dev/null @@ -1,295 +0,0 @@ -""" -dsml - generate and parse DSMLv1 data -(see http://www.oasis-open.org/committees/dsml/) - -See https://www.python-ldap.org/ for details. - -Python compability note: -Tested with Python 2.0+. -""" - -__version__ = '2.5.0' - -import string,base64 - - -special_entities = ( - ('&','&'), - ('<','<'), - ('"','"'), - ("'",'''), -) - - -def replace_char(s): - for char,entity in special_entities: - s = string.replace(s,char,entity) - return s - - -class DSMLWriter: - """ - Class for writing LDAP entry records to a DSMLv1 file. - - Arguments: - - f - File object for output. - base64_attrs - Attribute types to be base64-encoded. - dsml_comment - Text placed in comment lines behind . - indent - String used for indentiation of next nested level. - """ - - def __init__( - self,f,base64_attrs=[],dsml_comment='',indent=' ' - ): - self._output_file = f - self._base64_attrs = {}.fromkeys(map(string.lower,base64_attrs)) - self._dsml_comment = dsml_comment - self._indent = indent - - def _needs_base64_encoding(self,attr_type,attr_value): - if self._base64_attrs: - return self._base64_attrs.has_key(string.lower(attr_type)) - else: - try: - unicode(attr_value,'utf-8') - except UnicodeError: - return 1 - else: - return 0 - - def writeHeader(self): - """ - Write the header - """ - self._output_file.write('\n'.join([ - '', - '', - '', - '%s\n' % (self._indent), - ]) - ) - if self._dsml_comment: - self._output_file.write('%s\n' % (self._indent)) - - def writeFooter(self): - """ - Write the footer - """ - self._output_file.write('%s\n' % (self._indent)) - self._output_file.write('\n') - - def unparse(self,dn,entry): - return self.writeRecord(dn,entry) - - def writeRecord(self,dn,entry): - """ - dn - string-representation of distinguished name - entry - dictionary holding the LDAP entry {attr:data} - """ - - # Write line dn: first - self._output_file.write( - '%s\n' % ( - self._indent*2,replace_char(dn) - ) - ) - - objectclasses = entry.get('objectclass',entry.get('objectClass',[])) - - self._output_file.write('%s\n' % (self._indent*3)) - for oc in objectclasses: - self._output_file.write('%s%s\n' % (self._indent*4,oc)) - self._output_file.write('%s\n' % (self._indent*3)) - - attr_types = entry.keys()[:] - try: - attr_types.remove('objectclass') - attr_types.remove('objectClass') - except ValueError: - pass - attr_types.sort() - for attr_type in attr_types: - self._output_file.write('%s\n' % (self._indent*3,attr_type)) - for attr_value_item in entry[attr_type]: - needs_base64_encoding = self._needs_base64_encoding( - attr_type,attr_value_item - ) - if needs_base64_encoding: - attr_value_item = base64.encodestring(attr_value_item) - else: - attr_value_item = replace_char(attr_value_item) - self._output_file.write('%s\n' % ( - self._indent*4, - ' encoding="base64"'*needs_base64_encoding - ) - ) - self._output_file.write('%s%s\n' % ( - self._indent*5, - attr_value_item - ) - ) - self._output_file.write('%s\n' % ( - self._indent*4, - ) - ) - self._output_file.write('%s\n' % (self._indent*3)) - self._output_file.write('%s\n' % (self._indent*2)) - return - - -try: - - import xml.sax,xml.sax.handler - -except ImportError: - pass - -else: - - class DSMLv1Handler(xml.sax.handler.ContentHandler): - """ - Content handler class for DSMLv1 - """ - - def __init__(self,parser_instance): - self._parser_instance = parser_instance - xml.sax.handler.ContentHandler.__init__(self) - - def startDocument(self): - pass - - def endDocument(self): - pass - - def startElement(self,raw_name,attrs): - assert raw_name.startswith(''),'Illegal name' - name = raw_name[5:] - if name=='dsml': - pass - elif name=='directory-entries': - self._parsing_entries = 1 - elif name=='entry': - self._dn = attrs['dn'] - self._entry = {} - elif name=='attr': - self._attr_type = attrs['name'].encode('utf-8') - self._attr_values = [] - elif name=='value': - self._attr_value = '' - self._base64_encoding = attrs.get('encoding','').lower()=='base64' - # Handle object class tags - elif name=='objectclass': - self._object_classes = [] - elif name=='oc-value': - self._oc_value = '' - # Unhandled tags - else: - raise ValueError,'Unknown tag %s' % (raw_name) - - def endElement(self,raw_name): - assert raw_name.startswith('dsml:'),'Illegal name' - name = raw_name[5:] - if name=='dsml': - pass - elif name=='directory-entries': - self._parsing_entries = 0 - elif name=='entry': - self._parser_instance.handle(self._dn,self._entry) - del self._dn - del self._entry - elif name=='attr': - self._entry[self._attr_type] = self._attr_values - del self._attr_type - del self._attr_values - elif name=='value': - if self._base64_encoding: - attr_value = base64.decodestring(self._attr_value.strip()) - else: - attr_value = self._attr_value.strip().encode('utf-8') - self._attr_values.append(attr_value) - del attr_value - del self._attr_value - del self._base64_encoding - # Handle object class tags - elif name=='objectclass': - self._entry['objectClass'] = self._object_classes - del self._object_classes - elif name=='oc-value': - self._object_classes.append(self._oc_value.strip().encode('utf-8')) - del self._oc_value - # Unhandled tags - else: - raise ValueError,'Unknown tag %s' % (raw_name) - - def characters(self,ch): - if self.__dict__.has_key('_oc_value'): - self._oc_value = self._oc_value + ch - elif self.__dict__.has_key('_attr_value'): - self._attr_value = self._attr_value + ch - else: - pass - - - class DSMLParser: - """ - Base class for a DSMLv1 parser. Applications should sub-class this - class and override method handle() to implement something meaningful. - - Public class attributes: - - records_read - Counter for records processed so far - - Arguments: - - input_file - File-object to read the DSMLv1 input from - ignored_attr_types - Attributes with these attribute type names will be ignored. - max_entries - If non-zero specifies the maximum number of entries to be - read from f. - line_sep - String used as line separator - """ - - def __init__( - self, - input_file, - ContentHandlerClass, - ignored_attr_types=None, - max_entries=0, - ): - self._input_file = input_file - self._max_entries = max_entries - self._ignored_attr_types = {}.fromkeys(map(string.lower,(ignored_attr_types or []))) - self._current_record = None,None - self.records_read = 0 - self._parser = xml.sax.make_parser() - self._parser.setFeature(xml.sax.handler.feature_namespaces,0) - content_handler = ContentHandlerClass(self) - self._parser.setContentHandler(content_handler) - - def handle(self,*args,**kwargs): - """ - Process a single DSMLv1 entry record. This method should be - implemented by applications using DSMLParser. - """ - import pprint - pprint.pprint(args) - pprint.pprint(kwargs) - - def parse(self): - """ - Continously read and parse DSML records - """ - self._parser.parse(self._input_file) From a90c0374667ba9611fd9461165370e2e853531a7 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:23:35 +0000 Subject: [PATCH 012/121] there will never be DSMLv2 support --- TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/TODO b/TODO index 456cfeaf..b728424a 100644 --- a/TODO +++ b/TODO @@ -20,7 +20,6 @@ Lib/ (Everybody asking for the latter should check the mailing list archive first.) - Caching of search requests for each LDAPObject instance - LDIF parser for replication logs and change records -- DSMLv2 support Tests/ - Clean up and finish the mess of small test scripts started. From c4a612aa035089b05295e75eeaed8f7a1132d87b Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:24:17 +0000 Subject: [PATCH 013/121] class ldap.async.DSMLWriter removed too --- Doc/ldap-async.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/Doc/ldap-async.rst b/Doc/ldap-async.rst index d1c80874..59a34f43 100644 --- a/Doc/ldap-async.rst +++ b/Doc/ldap-async.rst @@ -27,9 +27,6 @@ Classes .. autoclass:: ldap.async.LDIFWriter :members: -.. autoclass:: ldap.async.DSMLWriter - :members: - .. _ldap.async-example: Examples From 7f76d8c45b3331e8536ab3c3768eef73a701bc23 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:24:51 +0000 Subject: [PATCH 014/121] removed stand-alone module dsml --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 53fab22b..f43da520 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,7 @@ pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ as installation prerequisites. Lib/ +* removed stand-alone module dsml * slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data * Compability changes for pyasn1 0.3.x or newer From 26c44435c85de3b281f90eeee1d9e6c6b54f4258 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:29:00 +0000 Subject: [PATCH 015/121] removed unused code schema.c --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index f43da520..708a7e41 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,9 @@ This release now strictly requires pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ as installation prerequisites. +Modules/ +* removed unused code schema.c + Lib/ * removed stand-alone module dsml * slapdtest.SlapdObject.restart() just restarts slapd From af29cbaa18707298e49b2b4c1240f7ee7382ed16 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:40:31 +0000 Subject: [PATCH 016/121] removed unused code schema.c --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index b75b7d17..9e625ff7 100644 --- a/setup.py +++ b/setup.py @@ -120,7 +120,6 @@ class OpenLDAP2: 'Modules/constants.c', 'Modules/errors.c', 'Modules/functions.c', - 'Modules/schema.c', 'Modules/ldapmodule.c', 'Modules/message.c', 'Modules/version.c', From 47bbbcf6ef2be92438d89eddd32014ced511a296 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:44:49 +0000 Subject: [PATCH 017/121] removed schema.h and LDAPinit_schema --- Modules/ldapmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 7d8ed2f8..25ea3fe1 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -39,7 +39,6 @@ init_ldap() LDAPinit_constants(d); LDAPinit_errors(d); LDAPinit_functions(d); - LDAPinit_schema(d); LDAPinit_control(d); /* Check for errors */ From 03e5e7a133f77f19a480df29ae92c19d3efae2b9 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:45:12 +0000 Subject: [PATCH 018/121] removed schema.h and LDAPinit_schema --- Modules/ldapmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 25ea3fe1..a570220b 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -5,7 +5,6 @@ #include "constants.h" #include "errors.h" #include "functions.h" -#include "schema.h" #include "ldapcontrol.h" #include "LDAPObject.h" From c0a269f60f5612dc8245a396068ff0dc949dcf3d Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 13:46:13 +0000 Subject: [PATCH 019/121] added distclean.sh --- distclean.sh | 5 +++++ 1 file changed, 5 insertions(+) create mode 100755 distclean.sh diff --git a/distclean.sh b/distclean.sh new file mode 100755 index 00000000..25851165 --- /dev/null +++ b/distclean.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +python setup.py clean +rm -r MANIFEST dist/* build/* Lib/*.egg-info .tox +rm Lib/_ldap.so Lib/*.py? Lib/ldap/*.py? Lib/ldap/*/*.py? Tests/*.py? *.py? From e01cd1686f54e5534fa8e90cb383bca4a08204d5 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 14:08:13 +0000 Subject: [PATCH 020/121] ldap.__version__, ldap.__author__ and ldap.__license__ now imported from new sub-module ldap.pkginfo also to setup.py --- CHANGES | 2 ++ Lib/ldap/__init__.py | 2 +- Lib/ldap/pkginfo.py | 7 +++++++ setup.py | 24 ++++++------------------ 4 files changed, 16 insertions(+), 19 deletions(-) create mode 100644 Lib/ldap/pkginfo.py diff --git a/CHANGES b/CHANGES index 708a7e41..dbdc048f 100644 --- a/CHANGES +++ b/CHANGES @@ -11,6 +11,8 @@ Modules/ * removed unused code schema.c Lib/ +* ldap.__version__, ldap.__author__ and ldap.__license__ now + imported from new sub-module ldap.pkginfo also to setup.py * removed stand-alone module dsml * slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index af83b6c5..e679de54 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -6,7 +6,7 @@ # This is also the overall release version number -__version__ = '2.5.0' +from pkginfo import __version__, __author__, __license__ import sys diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py new file mode 100644 index 00000000..6218b9de --- /dev/null +++ b/Lib/ldap/pkginfo.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +""" +meta attributes for packaging which does not import any dependencies +""" +__version__ = '2.5.0' +__author__ = u'python-ldap project' +__license__ = 'Python style' diff --git a/setup.py b/setup.py index 9e625ff7..329de0c2 100644 --- a/setup.py +++ b/setup.py @@ -14,20 +14,8 @@ from ConfigParser import ConfigParser import sys,os,string,time -################################################################## -# Weird Hack to grab release version of python-ldap from local dir -################################################################## -exec_startdir = os.path.dirname(os.path.abspath(sys.argv[0])) -package_init_file_name = reduce(os.path.join,[exec_startdir,'Lib','ldap','__init__.py']) -f = open(package_init_file_name,'r') -s = f.readline() -while s: - s = string.strip(s) - if s[0:11]=='__version__': - version = eval(string.split(s,'=')[1]) - break - s = f.readline() -f.close() +sys.path.insert(0, os.path.join(os.getcwd(), 'Lib/ldap')) +import pkginfo #-- A class describing the features and requirements of OpenLDAP 2.0 class OpenLDAP2: @@ -78,7 +66,8 @@ class OpenLDAP2: setup( #-- Package description name = name, - version = version, + license=pkginfo.__license__, + version=pkginfo.__version__, description = 'Python modules for implementing LDAP clients', long_description = """python-ldap: python-ldap provides an object-oriented API to access LDAP directory servers @@ -87,7 +76,7 @@ class OpenLDAP2: (e.g. processing LDIF, LDAPURLs, LDAPv3 schema, LDAPv3 extended operations and controls, etc.). """, - author = 'python-ldap project', + author = pkginfo.__author__, author_email = 'python-ldap@python.org', url = 'https://www.python-ldap.org/', download_url = 'https://pypi.python.org/pypi/python-ldap/', @@ -108,7 +97,6 @@ class OpenLDAP2: 'Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP', 'License :: OSI Approved :: Python Software Foundation License', ], - license = 'Python style', #-- C extension modules ext_modules = [ Extension( @@ -137,7 +125,7 @@ class OpenLDAP2: ('ldap_r' in LDAP_CLASS.libs or 'oldap_r' in LDAP_CLASS.libs)*[('HAVE_LIBLDAP_R',None)] + \ ('sasl' in LDAP_CLASS.libs or 'sasl2' in LDAP_CLASS.libs or 'libsasl' in LDAP_CLASS.libs)*[('HAVE_SASL',None)] + \ ('ssl' in LDAP_CLASS.libs and 'crypto' in LDAP_CLASS.libs)*[('HAVE_TLS',None)] + \ - [('LDAPMODULE_VERSION', version)] + [('LDAPMODULE_VERSION', pkginfo.__version__)] ), ], #-- Python "stand alone" modules From c9390f8e30b00fc91034fae741135cca8d36a837 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 14:11:41 +0000 Subject: [PATCH 021/121] removed schema.c and schema.h --- Modules/schema.c | 282 ----------------------------------------------- Modules/schema.h | 13 --- 2 files changed, 295 deletions(-) delete mode 100644 Modules/schema.c delete mode 100644 Modules/schema.h diff --git a/Modules/schema.c b/Modules/schema.c deleted file mode 100644 index fb38b0aa..00000000 --- a/Modules/schema.c +++ /dev/null @@ -1,282 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#include "common.h" - -#include "schema.h" -#include "ldap_schema.h" - -/* - This utility function takes a null delimited C array of (null - delimited) C strings, creates its python equivalent and returns a - new reference to it. If the array is empty or the pointer to it is - NULL, an empty python array is returned. -*/ -PyObject* c_string_array_to_python(char **string_array) -{ - Py_ssize_t count = 0; - char **s; - PyObject *py_list; - if (string_array) { - for (s=string_array; *s != 0; s++) count++; - py_list = PyList_New(count); - count = 0; - for (s=string_array; *s != 0; s++){ - PyList_SetItem(py_list, count, PyString_FromString(*s)); - count++; - } - } else py_list=PyList_New(0); - return py_list; -} - - -/* - This function returns a list of tuples. The first entry of each - tuple is a string (lsei_name), and the second is a lists built from - lsei_values. - - Probably the C data structure is modeled along the lines of a - mapping "lsei_name -> (list of lsei_values)". However, there seems - to be no guarantee that a lsei_name is unique, so I dare not use a - python mapping for this beast... - */ -PyObject* schema_extension_to_python(LDAPSchemaExtensionItem **extensions) -{ - Py_ssize_t count = 0; - LDAPSchemaExtensionItem **e; - PyObject *py_list, *item_tuple; - if (extensions) { - for (e = extensions; *e !=0; e++) count++; - py_list = PyList_New(count); - count = 0; - for (e = extensions; *e !=0; e++) { - item_tuple = PyTuple_New(2); - PyTuple_SetItem(item_tuple, 0, - PyString_FromString((*e)->lsei_name)); - PyTuple_SetItem(item_tuple, 1, - c_string_array_to_python((*e)->lsei_values)); - PyList_SetItem(py_list, count, item_tuple); - count++; - } - } - else py_list=PyList_New(0); - return py_list; -} - - -/* - The following four functions do the boring job: they take a python - string, feed it into the respective parser functions provided by - openldap, and build a python list from the data structure returned - by the C function. - */ - -static char doc_ldap_str2objectclass[] = -""; - -static PyObject* -l_ldap_str2objectclass(PyObject* self, PyObject *args) -{ - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - char *oc_string; - const char *errp; - LDAPObjectClass *o; - PyObject *oc_names, *oc_sup_oids, *oc_at_oids_must, - *oc_at_oids_may, *py_ret; - - - if (!PyArg_ParseTuple(args, "si", &oc_string, &flag)) - return NULL; - o = ldap_str2objectclass( oc_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - - oc_sup_oids = c_string_array_to_python(o->oc_sup_oids); - oc_names = c_string_array_to_python(o->oc_names); - oc_at_oids_must = c_string_array_to_python(o->oc_at_oids_must); - oc_at_oids_may = c_string_array_to_python(o->oc_at_oids_may); - py_ret = PyList_New(9); - PyList_SetItem(py_ret, 0, PyString_FromString(o->oc_oid)); - PyList_SetItem(py_ret, 1, oc_names); - if (o->oc_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(o->oc_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(o->oc_obsolete)); - PyList_SetItem(py_ret, 4, oc_sup_oids); - PyList_SetItem(py_ret, 5, PyInt_FromLong(o->oc_kind)); - PyList_SetItem(py_ret, 6, oc_at_oids_must); - PyList_SetItem(py_ret, 7, oc_at_oids_may); - - PyList_SetItem(py_ret, 8, - schema_extension_to_python(o->oc_extensions)); - - ldap_objectclass_free(o); - return py_ret; -} - - -static char doc_ldap_str2attributetype[] = -""; - -static PyObject* -l_ldap_str2attributetype(PyObject* self, PyObject *args) -{ - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - char *at_string; - const char *errp; - LDAPAttributeType *a; - PyObject *py_ret; - PyObject *at_names; - - if (!PyArg_ParseTuple(args, "si", &at_string,&flag)) - return NULL; - a = ldap_str2attributetype( at_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - - py_ret = PyList_New(15); - PyList_SetItem(py_ret, 0, PyString_FromString(a->at_oid)); - at_names = c_string_array_to_python(a->at_names); - PyList_SetItem(py_ret, 1, at_names); - if (a->at_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(a->at_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(a->at_obsolete)); - if (a->at_sup_oid) { - PyList_SetItem(py_ret, 4, PyString_FromString(a->at_sup_oid)); - } else { - PyList_SetItem(py_ret, 4, PyString_FromString("")); - } - if (a->at_equality_oid) { - PyList_SetItem(py_ret, 5, PyString_FromString(a->at_equality_oid)); - } else { - PyList_SetItem(py_ret, 5, PyString_FromString("")); - } - if (a->at_ordering_oid) { - PyList_SetItem(py_ret, 6, PyString_FromString(a->at_ordering_oid)); - } else { - PyList_SetItem(py_ret, 6, PyString_FromString("")); - } - if (a->at_substr_oid) { - PyList_SetItem(py_ret, 7, PyString_FromString(a->at_substr_oid)); - } else { - PyList_SetItem(py_ret, 7, PyString_FromString("")); - } - if (a->at_syntax_oid) { - PyList_SetItem(py_ret, 8, PyString_FromString(a->at_syntax_oid)); - } else { - PyList_SetItem(py_ret, 8, PyString_FromString("")); - } - PyList_SetItem(py_ret, 9, PyInt_FromLong(a->at_syntax_len)); - PyList_SetItem(py_ret,10, PyInt_FromLong(a->at_single_value)); - PyList_SetItem(py_ret,11, PyInt_FromLong(a->at_collective)); - PyList_SetItem(py_ret,12, PyInt_FromLong(a->at_no_user_mod)); - PyList_SetItem(py_ret,13, PyInt_FromLong(a->at_usage)); - - PyList_SetItem(py_ret, 14, - schema_extension_to_python(a->at_extensions)); - ldap_attributetype_free(a); - return py_ret; -} - -static char doc_ldap_str2syntax[] = -""; - - -static PyObject* -l_ldap_str2syntax(PyObject* self, PyObject *args) -{ - LDAPSyntax *s; - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - const char *errp; - char *syn_string; - PyObject *py_ret, *syn_names; - - if (!PyArg_ParseTuple(args, "si", &syn_string,&flag)) - return NULL; - s = ldap_str2syntax(syn_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - py_ret = PyList_New(4); - PyList_SetItem(py_ret, 0, PyString_FromString(s->syn_oid)); - syn_names = c_string_array_to_python(s->syn_names); - PyList_SetItem(py_ret, 1, syn_names); - if (s->syn_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(s->syn_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, - schema_extension_to_python(s->syn_extensions)); - ldap_syntax_free(s); - return py_ret; -} - -static char doc_ldap_str2matchingrule[] = -""; - -static PyObject* -l_ldap_str2matchingrule(PyObject* self, PyObject *args) -{ - LDAPMatchingRule *m; - int ret=0, flag = LDAP_SCHEMA_ALLOW_NONE; - const char *errp; - char *mr_string; - PyObject *py_ret, *mr_names; - - if (!PyArg_ParseTuple(args, "si", &mr_string,&flag)) - return NULL; - m = ldap_str2matchingrule(mr_string, &ret, &errp, flag); - if (ret) { - py_ret = PyInt_FromLong(ret); - return py_ret; - } - py_ret = PyList_New(6); - PyList_SetItem(py_ret, 0, PyString_FromString(m->mr_oid)); - mr_names = c_string_array_to_python(m->mr_names); - PyList_SetItem(py_ret, 1, mr_names); - if (m->mr_desc) { - PyList_SetItem(py_ret, 2, PyString_FromString(m->mr_desc)); - } else { - PyList_SetItem(py_ret, 2, PyString_FromString("")); - } - PyList_SetItem(py_ret, 3, PyInt_FromLong(m->mr_obsolete)); - if (m->mr_syntax_oid) { - PyList_SetItem(py_ret, 4, PyString_FromString(m->mr_syntax_oid)); - } else { - PyList_SetItem(py_ret, 4, PyString_FromString("")); - } - PyList_SetItem(py_ret, 5, - schema_extension_to_python(m->mr_extensions)); - ldap_matchingrule_free(m); - return py_ret; -} - -/* methods */ - -static PyMethodDef methods[] = { - { "str2objectclass", (PyCFunction)l_ldap_str2objectclass, METH_VARARGS, - doc_ldap_str2objectclass }, - { "str2attributetype", (PyCFunction)l_ldap_str2attributetype, - METH_VARARGS, doc_ldap_str2attributetype }, - { "str2syntax", (PyCFunction)l_ldap_str2syntax, - METH_VARARGS, doc_ldap_str2syntax }, - { "str2matchingrule", (PyCFunction)l_ldap_str2matchingrule, - METH_VARARGS, doc_ldap_str2matchingrule }, - { NULL, NULL } -}; - - -void -LDAPinit_schema( PyObject* d ) { - LDAPadd_methods( d, methods ); -} diff --git a/Modules/schema.h b/Modules/schema.h deleted file mode 100644 index eee00a3e..00000000 --- a/Modules/schema.h +++ /dev/null @@ -1,13 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_schema_ -#define __h_schema_ - - - -#include "common.h" -extern void LDAPinit_schema( PyObject* ); - - -#endif /* __h_schema_ */ - From 79cc6976ba2c0cea598da5b27663c9eb1330a42b Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 14:21:14 +0000 Subject: [PATCH 022/121] For 2.5.x mandatory prerequisites are: Python 2.7.x, pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ --- CHANGES | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index dbdc048f..ed9eb142 100644 --- a/CHANGES +++ b/CHANGES @@ -3,9 +3,9 @@ Released 2.5.0 2017-11-xx Changes since 2.4.45: -This release now strictly requires -pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ -as installation prerequisites. +Mandatory prerequisites: +- Python 2.7.x +- pyasn1 0.3.7+ and pyasn1_modules 0.1.5+ Modules/ * removed unused code schema.c From 0f8e8ba00c4e003324271bfe6cf293315f54b1ee Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 15:47:48 +0000 Subject: [PATCH 023/121] corrected Test02_ReconnectLDAPObject.__doc__ --- Tests/t_ldapobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index b7836777..e03ca1e2 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -191,7 +191,7 @@ def test_sasl_extenal_bind_s(self): class Test02_ReconnectLDAPObject(Test01_SimpleLDAPObject): """ - test LDAP search operations + test ReconnectLDAPObject by restarting slapd """ ldap_object_class = ReconnectLDAPObject From 072f5cf1e184551425ab96c781d1c6af72c63bd3 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 16:30:27 +0000 Subject: [PATCH 024/121] Added safety assertion when importing _ldap: ldap.pkginfo.__version__ must match _ldap.__version__ --- CHANGES | 2 ++ Lib/ldap/__init__.py | 4 ++++ Lib/ldap/controls/__init__.py | 21 +++++++++++++-------- Lib/ldap/controls/libldap.py | 9 ++++++++- Lib/ldap/dn.py | 5 +++-- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/CHANGES b/CHANGES index ed9eb142..b64b7820 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Modules/ Lib/ * ldap.__version__, ldap.__author__ and ldap.__license__ now imported from new sub-module ldap.pkginfo also to setup.py +* Added safety assertion when importing _ldap: + ldap.pkginfo.__version__ must match _ldap.__version__ * removed stand-alone module dsml * slapdtest.SlapdObject.restart() just restarts slapd without cleaning any data diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index e679de54..c1c5ef17 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -17,7 +17,11 @@ _trace_file = sys.stderr _trace_stack_limit = None +from ldap.pkginfo import __version__ + import _ldap +assert _ldap.__version__==__version__, \ + ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) from _ldap import * OPT_NAMES_DICT = {} diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index d504d49b..56d611b9 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -9,7 +9,19 @@ Each class provides support for a certain control. """ -from ldap import __version__ +from ldap.pkginfo import __version__ + +import _ldap +assert _ldap.__version__==__version__, \ + ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + +import ldap + +try: + from pyasn1.error import PyAsn1Error +except ImportError: + PyAsn1Error = None + __all__ = [ 'KNOWN_RESPONSE_CONTROLS', @@ -32,13 +44,6 @@ # response control OID to class registry KNOWN_RESPONSE_CONTROLS = {} -import _ldap,ldap - -try: - from pyasn1.error import PyAsn1Error -except ImportError: - PyAsn1Error = None - class RequestControl: """ diff --git a/Lib/ldap/controls/libldap.py b/Lib/ldap/controls/libldap.py index 53a02a40..f6ea42c4 100644 --- a/Lib/ldap/controls/libldap.py +++ b/Lib/ldap/controls/libldap.py @@ -6,7 +6,14 @@ See https://www.python-ldap.org/ for details. """ -import _ldap,ldap +from ldap.pkginfo import __version__ + +import _ldap +assert _ldap.__version__==__version__, \ + ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + +import ldap + from ldap.controls import RequestControl,LDAPControl,KNOWN_RESPONSE_CONTROLS diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 75465ed1..daab8ab7 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -7,10 +7,11 @@ - Tested with Python 2.0+ """ -from ldap import __version__ - +from ldap.pkginfo import __version__ import _ldap +assert _ldap.__version__==__version__, \ + ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) import ldap.functions From 65e9271db26bc2e3e4ddeed91c3089f221946baa Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 17:17:48 +0000 Subject: [PATCH 025/121] * The methods SSSResponseControl.decodeControlValue() and VLVResponseControl.decodeControlValue() now follow the coding convention to use camel-cased ASN.1 name as class attribute name. The old class names are still set for back-ward compability but should not be used in new code because they might be removed in a later release. * removed SSSRequestControl from ldap.controls.KNOWN_RESPONSE_CONTROLS --- CHANGES | 7 +++++++ Lib/ldap/controls/sss.py | 19 +++++++++++-------- Lib/ldap/controls/vlv.py | 20 +++++++++++++------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index b64b7820..e41032b6 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,13 @@ Lib/ without cleaning any data * Compability changes for pyasn1 0.3.x or newer (thanks to Ilya Etingof and Christian Heimes) +* The methods SSSResponseControl.decodeControlValue() and + VLVResponseControl.decodeControlValue() now follow the coding + convention to use camel-cased ASN.1 name as class attribute name. + The old class names are still set for back-ward compability + but should not be used in new code because they might be removed + in a later release. +* removed SSSRequestControl from ldap.controls.KNOWN_RESPONSE_CONTROLS Tests/ * added explicit reconnect tests for ReconnectLDAPObject diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 20697faf..7899f04d 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -119,14 +119,17 @@ def __init__(self,criticality=False): def decodeControlValue(self, encoded): p, rest = decoder.decode(encoded, asn1Spec=SortResultType()) assert not rest, 'all data could not be decoded' - self.result = int(p.getComponentByName('sortResult')) - self.result_code = p.getComponentByName('sortResult').prettyOut(self.result) - attribute_type_error = p.getComponentByName('attributeType') - if attribute_type_error.hasValue(): - self.attribute_type_error = attribute_type_error + sort_result = p.getComponentByName('sortResult') + self.sortResult = int(sort_result) + attribute_type = p.getComponentByName('attributeType') + if attribute_type.hasValue(): + self.attributeType = attribute_type else: - self.attribute_type_error = None + self.attributeType = None + # backward compability class attributes + self.result = self.sortResult + self.attribute_type_error = self.attributeType + # not sure whether to keep this + self.result_code = sort_result.prettyPrint() - -KNOWN_RESPONSE_CONTROLS[SSSRequestControl.controlType] = SSSRequestControl KNOWN_RESPONSE_CONTROLS[SSSResponseControl.controlType] = SSSResponseControl diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index 980b77ba..4b3d931e 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -125,15 +125,21 @@ def __init__(self,criticality=False): def decodeControlValue(self,encoded): p, rest = decoder.decode(encoded, asn1Spec=VirtualListViewResponseType()) assert not rest, 'all data could not be decoded' - self.target_position = int(p.getComponentByName('targetPosition')) - self.content_count = int(p.getComponentByName('contentCount')) - self.result = int(p.getComponentByName('virtualListViewResult')) - self.result_code = p.getComponentByName('virtualListViewResult').prettyOut(self.result) + self.targetPosition = int(p.getComponentByName('targetPosition')) + self.contentCount = int(p.getComponentByName('contentCount')) + virtual_list_view_result = p.getComponentByName('virtualListViewResult') + self.virtualListViewResult = int(virtual_list_view_result) context_id = p.getComponentByName('contextID') if context_id.hasValue(): - self.context_id = str(context_id) + self.contextID = str(context_id) else: - self.context_id = None - + self.contextID = None + # backward compability class attributes + self.target_position = self.targetPosition + self.content_count = self.contentCount + self.result = self.virtualListViewResult + self.context_id = self.contextID + # not sure whether to keep this + self.result_code = virtual_list_view_result.prettyPrint() KNOWN_RESPONSE_CONTROLS[VLVResponseControl.controlType] = VLVResponseControl From 58e1f26ffe23f0258f6fbb6533c8a726451cfb24 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 17:31:36 +0000 Subject: [PATCH 026/121] added missing ldap.pkginfo in setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 329de0c2..962d7630 100644 --- a/setup.py +++ b/setup.py @@ -155,6 +155,7 @@ class OpenLDAP2: 'ldap.ldapobject', 'ldap.logger', 'ldap.modlist', + 'ldap.pkginfo', 'ldap.resiter', 'ldap.sasl', 'ldap.schema', From 6b1063e45754baa6e24e7b2b40869f29bfb5f170 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 12 Nov 2017 17:34:22 +0000 Subject: [PATCH 027/121] prepare release 2.5.1 --- CHANGES | 2 +- Doc/conf.py | 2 +- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index e41032b6..b706563e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ ---------------------------------------------------------------- -Released 2.5.0 2017-11-xx +Released 2.5.1 2017-11-12 Changes since 2.4.45: diff --git a/Doc/conf.py b/Doc/conf.py index 4e603c40..ab7c509e 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -42,7 +42,7 @@ # The short X.Y version. version = '2.5' # The full version, including alpha/beta/rc tags. -release = '2.5.0.0' +release = '2.5.1.0' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 6218b9de..39a572bd 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '2.5.0' +__version__ = '2.5.1' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 07a6863d..9b6f2520 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -9,7 +9,7 @@ 2. list comprehensions are used. """ -__version__ = '2.5.0' +__version__ = '2.5.1' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 2de56504..d00042cd 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -7,7 +7,7 @@ Tested with Python 2.0+, but should work with Python 1.5.2+. """ -__version__ = '2.5.0' +__version__ = '2.5.1' __all__ = [ # constants diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index d5e6a828..5b438bac 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -8,7 +8,7 @@ This module only works with Python 2.7.x since """ -__version__ = '2.5.0' +__version__ = '2.5.1' import os import socket From 26c2564a6a1feb75a12a3a268013010fd8153d29 Mon Sep 17 00:00:00 2001 From: stroeder Date: Thu, 16 Nov 2017 16:08:57 +0000 Subject: [PATCH 028/121] started 2.5.2 --- CHANGES | 10 ++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest.py | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b706563e..9534aeb5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,13 @@ +---------------------------------------------------------------- +Released 2.5.2 2017-11-xx + +Changes since 2.5.1: + +Lib/ + +Tests/ +* + ---------------------------------------------------------------- Released 2.5.1 2017-11-12 diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 39a572bd..da891b3d 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -2,6 +2,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '2.5.1' +__version__ = '2.5.2' __author__ = u'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 9b6f2520..fa072c9e 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -9,7 +9,7 @@ 2. list comprehensions are used. """ -__version__ = '2.5.1' +__version__ = '2.5.2' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index d00042cd..c4ac4e86 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -7,7 +7,7 @@ Tested with Python 2.0+, but should work with Python 1.5.2+. """ -__version__ = '2.5.1' +__version__ = '2.5.2' __all__ = [ # constants diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 5b438bac..4381ae09 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -8,7 +8,7 @@ This module only works with Python 2.7.x since """ -__version__ = '2.5.1' +__version__ = '2.5.2' import os import socket From 6c65d12bf870759cffb91d66544adcb6a716e208 Mon Sep 17 00:00:00 2001 From: stroeder Date: Thu, 16 Nov 2017 16:10:35 +0000 Subject: [PATCH 029/121] Tests/ scripts do not directly call SlapdTestCase.setUpClass() anymore --- CHANGES | 2 +- Tests/t_cext.py | 2 +- Tests/t_ldapobject.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 9534aeb5..32b1c7cf 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Changes since 2.5.1: Lib/ Tests/ -* +* scripts do not directly call SlapdTestCase.setUpClass() anymore ---------------------------------------------------------------- Released 2.5.1 2017-11-12 diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 6add784c..e1d79d36 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -26,7 +26,7 @@ class TestLdapCExtension(SlapdTestCase): @classmethod def setUpClass(cls): - SlapdTestCase.setUpClass() + super(TestLdapCExtension, cls).setUpClass() # add two initial objects after server was started and is still empty suffix_dc = cls.server.suffix.split(',')[0][3:] cls.server._log.debug( diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index e03ca1e2..af83e464 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -65,7 +65,7 @@ class Test01_SimpleLDAPObject(SlapdTestCase): @classmethod def setUpClass(cls): - SlapdTestCase.setUpClass() + super(Test01_SimpleLDAPObject, cls).setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { From 20ca4e8bce043e138594af51b6210b226bdfdbb5 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 12:09:56 +0000 Subject: [PATCH 030/121] only use _ldap when setting LIBLDAP_API_INFO --- Lib/ldap/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index c1c5ef17..c8a1b121 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -23,6 +23,8 @@ assert _ldap.__version__==__version__, \ ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) from _ldap import * +# call into libldap to initialize it right now +LIBLDAP_API_INFO = _ldap.get_option(_ldap.OPT_API_INFO) OPT_NAMES_DICT = {} for k,v in vars(_ldap).items(): From 9eff3672f68f55d8c19f17c69162c6b5eb550b3a Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 12:12:53 +0000 Subject: [PATCH 031/121] moved code from version.c to ldapmodule.c and removed version.[ch] --- Modules/version.c | 19 ------------------- Modules/version.h | 11 ----------- setup.py | 1 - 3 files changed, 31 deletions(-) delete mode 100644 Modules/version.c delete mode 100644 Modules/version.h diff --git a/Modules/version.c b/Modules/version.c deleted file mode 100644 index b706b8ea..00000000 --- a/Modules/version.c +++ /dev/null @@ -1,19 +0,0 @@ -/* Set release version - * See https://www.python-ldap.org/ for details. */ - -#include "common.h" - -#define _STR(x) #x -#define STR(x) _STR(x) - -static char version_str[] = STR(LDAPMODULE_VERSION); - -void -LDAPinit_version( PyObject* d ) -{ - PyObject *version; - - version = PyString_FromString(version_str); - PyDict_SetItemString( d, "__version__", version ); - Py_DECREF(version); -} diff --git a/Modules/version.h b/Modules/version.h deleted file mode 100644 index 4d76b341..00000000 --- a/Modules/version.h +++ /dev/null @@ -1,11 +0,0 @@ -/* Set release version - * See https://www.python-ldap.org/ for details. */ - -#ifndef __h_version_ -#define __h_version_ - - -#include "common.h" -extern void LDAPinit_version( PyObject* d ); - -#endif /* __h_version_ */ diff --git a/setup.py b/setup.py index 962d7630..808d4eb9 100644 --- a/setup.py +++ b/setup.py @@ -110,7 +110,6 @@ class OpenLDAP2: 'Modules/functions.c', 'Modules/ldapmodule.c', 'Modules/message.c', - 'Modules/version.c', 'Modules/options.c', 'Modules/berval.c', ], From 1721df193a64f77933dfcc5144ee2460fc5bbb83 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 12:23:12 +0000 Subject: [PATCH 032/121] removed obsolete back-ward compability constants from common.h --- Modules/common.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Modules/common.h b/Modules/common.h index e29e20dd..1ec232cb 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -20,13 +20,6 @@ #include #endif -/* Backwards compability with Python prior 2.5 */ -#if PY_VERSION_HEX < 0x02050000 -typedef int Py_ssize_t; -#define PY_SSIZE_T_MAX INT_MAX -#define PY_SSIZE_T_MIN INT_MIN -#endif - #include #define streq( a, b ) \ ( (*(a)==*(b)) && 0==strcmp(a,b) ) From aedfdd1e15633eb850e7dc316afe6318161204db Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Wed, 22 Nov 2017 13:28:47 +0100 Subject: [PATCH 033/121] Add _ldap.__version__ Cherry-picked from: b837b54 stripped trailing spaces from C source files --- Modules/ldapmodule.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index a570220b..c11bf725 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -1,7 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ #include "common.h" -#include "version.h" #include "constants.h" #include "errors.h" #include "functions.h" @@ -9,6 +8,21 @@ #include "LDAPObject.h" +#define _STR(x) #x +#define STR(x) _STR(x) + +static char version_str[] = STR(LDAPMODULE_VERSION); + +void +LDAPinit_version( PyObject* d ) +{ + PyObject *version; + + version = PyString_FromString(version_str); + PyDict_SetItemString( d, "__version__", version ); + Py_DECREF(version); +} + DL_EXPORT(void) init_ldap(void); /* dummy module methods */ From 98181dedfa3e23f4cf5ad5065e0eee2baac27a19 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 12:47:38 +0000 Subject: [PATCH 034/121] build checks whether LDAP_API_VERSION is OpenLDAP 2.4.x --- Modules/LDAPObject.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index 4223735d..a0adc3f0 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -7,8 +7,8 @@ #include "lber.h" #include "ldap.h" -#if LDAP_API_VERSION < 2000 -#error Current python-ldap requires OpenLDAP 2.x +#if LDAP_API_VERSION < 2040 +#error Current python-ldap requires OpenLDAP 2.4.x #endif #if PYTHON_API_VERSION < 1007 From b1485d689a88b0bbf790ac5ee55e59dfc138b721 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 13:14:35 +0000 Subject: [PATCH 035/121] setup.py: added ldap.controls.vlv to py_modules --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 808d4eb9..7ce8d9da 100644 --- a/setup.py +++ b/setup.py @@ -145,6 +145,7 @@ class OpenLDAP2: 'ldap.controls.sessiontrack', 'ldap.controls.simple', 'ldap.controls.sss', + 'ldap.controls.vlv', 'ldap.cidict', 'ldap.dn', 'ldap.extop', From f9a910d69661fa4715046966d5e45d12634038d9 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 13:15:57 +0000 Subject: [PATCH 036/121] removed setting class attribute result_code in SSSResponseControl.decodeControlValue() and VLVResponseControl.decodeControlValue --- Lib/ldap/controls/sss.py | 2 -- Lib/ldap/controls/vlv.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 7899f04d..5d4955d1 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -129,7 +129,5 @@ def decodeControlValue(self, encoded): # backward compability class attributes self.result = self.sortResult self.attribute_type_error = self.attributeType - # not sure whether to keep this - self.result_code = sort_result.prettyPrint() KNOWN_RESPONSE_CONTROLS[SSSResponseControl.controlType] = SSSResponseControl diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index 4b3d931e..74d107b3 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -139,7 +139,5 @@ def decodeControlValue(self,encoded): self.content_count = self.contentCount self.result = self.virtualListViewResult self.context_id = self.contextID - # not sure whether to keep this - self.result_code = virtual_list_view_result.prettyPrint() KNOWN_RESPONSE_CONTROLS[VLVResponseControl.controlType] = VLVResponseControl From 25f8e6c9f51473ca68197cf7a148cfb671a48f44 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 13:53:11 +0000 Subject: [PATCH 037/121] announce minor changes for 2.5.2 in Modules/ and Lib/ --- CHANGES | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES b/CHANGES index 32b1c7cf..8c6dde4a 100644 --- a/CHANGES +++ b/CHANGES @@ -3,7 +3,16 @@ Released 2.5.2 2017-11-xx Changes since 2.5.1: +Modules/ +* 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 + Lib/ +* 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 Tests/ * scripts do not directly call SlapdTestCase.setUpClass() anymore From c25fe474fd4e06a2833e0685904055fa5df2e3c5 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 13:53:46 +0000 Subject: [PATCH 038/121] white-space cleaning --- Modules/message.h | 1 - Modules/options.h | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Modules/message.h b/Modules/message.h index b4f60eb1..c3522ac3 100644 --- a/Modules/message.h +++ b/Modules/message.h @@ -10,4 +10,3 @@ extern PyObject* LDAPmessage_to_python( LDAP*ld, LDAPMessage*m, int add_ctrls, int add_intermediates ); #endif /* __h_message_ */ - diff --git a/Modules/options.h b/Modules/options.h index dd613206..570fdc15 100644 --- a/Modules/options.h +++ b/Modules/options.h @@ -1,7 +1,7 @@ /* See https://www.python-ldap.org/ for details. */ -int LDAP_optionval_by_name(const char *name); -int LDAP_set_option(LDAPObject *self, int option, PyObject *value); +int LDAP_optionval_by_name(const char *name); +int LDAP_set_option(LDAPObject *self, int option, PyObject *value); PyObject *LDAP_get_option(LDAPObject *self, int option); void set_timeval_from_double( struct timeval *tv, double d ); From 707744c21e241e71f79518dc77763b4ce62e4c90 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 14:23:48 +0000 Subject: [PATCH 039/121] always use bytes() for UUID() constructor in ldap.syncrepl --- CHANGES | 1 + Lib/ldap/syncrepl.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 8c6dde4a..f4be1fc3 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Lib/ * 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 Tests/ * scripts do not directly call SlapdTestCase.setUpClass() anymore diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 94a7217e..93a3a2af 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -130,7 +130,7 @@ class SyncStateControl(ResponseControl): def decodeControlValue(self, encodedControlValue): d = decoder.decode(encodedControlValue, asn1Spec = syncStateValue()) state = d[0].getComponentByName('state') - uuid = UUID(bytes=d[0].getComponentByName('entryUUID')) + uuid = UUID(bytes=bytes(d[0].getComponentByName('entryUUID'))) cookie = d[0].getComponentByName('cookie') if cookie.hasValue(): self.cookie = str(self.cookie) @@ -287,7 +287,7 @@ def __init__(self, encodedMessage): uuids = [] ids = comp.getComponentByName('syncUUIDs') for i in range(len(ids)): - uuid = UUID(bytes=str(ids.getComponentByPosition(i))) + uuid = UUID(bytes=bytes(ids.getComponentByPosition(i))) uuids.append(str(uuid)) val['syncUUIDs'] = uuids val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) From 5b35eef635596af20438b546d924a06a7c159895 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 14:58:05 +0000 Subject: [PATCH 040/121] C module _ldap now also gets __author__ and __license__ set from ldap.pkginfo same like __version__ --- CHANGES | 1 + Modules/constants.c | 6 ------ Modules/ldapmodule.c | 25 +++++++++++++++++++------ setup.py | 6 +++++- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index f4be1fc3..32550b63 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,7 @@ Modules/ * 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 Lib/ * new global constant ldap.LIBLDAP_API_INFO diff --git a/Modules/constants.c b/Modules/constants.c index 2c0ec1d3..06c249ad 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -280,12 +280,6 @@ LDAPinit_constants( PyObject* d ) add_int(d,URL_ERR_BADSCOPE); add_int(d,URL_ERR_MEM); - /* author */ - - author = PyString_FromString("python-ldap Project"); - PyDict_SetItemString(d, "__author__", author); - Py_DECREF(author); - /* add_int(d,LIBLDAP_R); */ #ifdef HAVE_LIBLDAP_R obj = PyInt_FromLong(1); diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index c11bf725..dc954d43 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -12,15 +12,28 @@ #define STR(x) _STR(x) static char version_str[] = STR(LDAPMODULE_VERSION); +static char author_str[] = STR(LDAPMODULE_AUTHOR); +static char license_str[] = STR(LDAPMODULE_LICENSE); void -LDAPinit_version( PyObject* d ) +init_pkginfo( PyObject* d ) { - PyObject *version; + PyObject *version; + PyObject *author; + PyObject *license; + + version = PyString_FromString(version_str); + PyDict_SetItemString( d, "__version__", version ); + Py_DECREF(version); + + author = PyString_FromString(author_str); + PyDict_SetItemString(d, "__author__", author); + Py_DECREF(author); + + license = PyString_FromString(license_str); + PyDict_SetItemString(d, "__license__", license); + Py_DECREF(license); - version = PyString_FromString(version_str); - PyDict_SetItemString( d, "__version__", version ); - Py_DECREF(version); } DL_EXPORT(void) init_ldap(void); @@ -48,7 +61,7 @@ init_ldap() /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); - LDAPinit_version(d); + init_pkginfo(d); LDAPinit_constants(d); LDAPinit_errors(d); LDAPinit_functions(d); diff --git a/setup.py b/setup.py index 7ce8d9da..841ee553 100644 --- a/setup.py +++ b/setup.py @@ -124,7 +124,11 @@ class OpenLDAP2: ('ldap_r' in LDAP_CLASS.libs or 'oldap_r' in LDAP_CLASS.libs)*[('HAVE_LIBLDAP_R',None)] + \ ('sasl' in LDAP_CLASS.libs or 'sasl2' in LDAP_CLASS.libs or 'libsasl' in LDAP_CLASS.libs)*[('HAVE_SASL',None)] + \ ('ssl' in LDAP_CLASS.libs and 'crypto' in LDAP_CLASS.libs)*[('HAVE_TLS',None)] + \ - [('LDAPMODULE_VERSION', pkginfo.__version__)] + [ + ('LDAPMODULE_VERSION', pkginfo.__version__), + ('LDAPMODULE_AUTHOR', pkginfo.__author__), + ('LDAPMODULE_LICENSE', pkginfo.__license__), + ] ), ], #-- Python "stand alone" modules From 082490a0b129619f873bfcadb17bf0bef244bf89 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 15:32:36 +0000 Subject: [PATCH 041/121] stick to naming convention with LDAPinit_pkginfo() --- Modules/ldapmodule.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index dc954d43..a788ae19 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -16,24 +16,23 @@ static char author_str[] = STR(LDAPMODULE_AUTHOR); static char license_str[] = STR(LDAPMODULE_LICENSE); void -init_pkginfo( PyObject* d ) +LDAPinit_pkginfo( PyObject* d ) { PyObject *version; PyObject *author; PyObject *license; version = PyString_FromString(version_str); - PyDict_SetItemString( d, "__version__", version ); - Py_DECREF(version); - - author = PyString_FromString(author_str); - PyDict_SetItemString(d, "__author__", author); - Py_DECREF(author); + author = PyString_FromString(author_str); + license = PyString_FromString(license_str); - license = PyString_FromString(license_str); - PyDict_SetItemString(d, "__license__", license); - Py_DECREF(license); + PyDict_SetItemString( d, "__version__", version ); + PyDict_SetItemString(d, "__author__", author); + PyDict_SetItemString(d, "__license__", license); + Py_DECREF(version); + Py_DECREF(author); + Py_DECREF(license); } DL_EXPORT(void) init_ldap(void); @@ -61,7 +60,7 @@ init_ldap() /* Add some symbolic constants to the module */ d = PyModule_GetDict(m); - init_pkginfo(d); + LDAPinit_pkginfo(d); LDAPinit_constants(d); LDAPinit_errors(d); LDAPinit_functions(d); From 862221709799d9f27b31e55e9dc2b0bd79ce2f14 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 16:30:49 +0000 Subject: [PATCH 042/121] removed almost all assert statements in ldap.schema.models, removed importing of types module --- Lib/ldap/schema/models.py | 54 ++------------------------------------- 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 383705c3..2e84f48c 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -8,14 +8,6 @@ from ldap.schema.tokenizer import split_tokens,extract_tokens -if __debug__: - from types import TupleType,StringType,IntType - try: - from types import BooleanType - except ImportError: - BooleanType = IntType - - NOT_HUMAN_READABLE_LDAP_SYNTAXES = { '1.3.6.1.4.1.1466.115.121.1.4':None, # Audio '1.3.6.1.4.1.1466.115.121.1.5':None, # Binary @@ -68,7 +60,7 @@ def get_id(self): return self.oid def key_attr(self,key,value,quoted=0): - assert value is None or type(value)==StringType,TypeError("value has to be of StringType, was %s" % repr(value)) + assert value is None or type(value)==str,TypeError("value has to be of str, was %r" % value) if value: if quoted: return " %s '%s'" % (key,value.replace("'","\\'")) @@ -78,7 +70,7 @@ def key_attr(self,key,value,quoted=0): return "" def key_list(self,key,values,sep=' ',quoted=0): - assert type(values)==TupleType,TypeError("values has to be of ListType") + assert type(values)==tuple,TypeError("values has to be a tuple, was %r" % values) if not values: return '' if quoted: @@ -159,13 +151,6 @@ def _set_attrs(self,l,d): self.sup = ('top',) else: self.sup = d['SUP'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.sup)==TupleType - assert type(self.kind)==IntType - assert type(self.must)==TupleType - assert type(self.may)==TupleType return def __str__(self): @@ -286,14 +271,6 @@ def _set_attrs(self,l,d): self.collective = d['COLLECTIVE']!=None self.no_user_mod = d['NO-USER-MODIFICATION']!=None self.usage = AttributeUsage.get(d['USAGE'][0],0) - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.sup)==TupleType,'attribute sup has type %s' % (type(self.sup)) - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.single_value)==BooleanType and (self.single_value==0 or self.single_value==1) - assert type(self.no_user_mod)==BooleanType and (self.no_user_mod==0 or self.no_user_mod==1) - assert self.syntax is None or type(self.syntax)==StringType - assert self.syntax_len is None or type(self.syntax_len)==type(0L) return def __str__(self): @@ -351,7 +328,6 @@ def _set_attrs(self,l,d): NOT_HUMAN_READABLE_LDAP_SYNTAXES.has_key(self.oid) or \ d['X-NOT-HUMAN-READABLE'][0]=='TRUE' self.x_binary_transfer_required = d['X-BINARY-TRANSFER-REQUIRED'][0]=='TRUE' - assert self.desc is None or type(self.desc)==StringType return def __str__(self): @@ -398,10 +374,6 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.obsolete = d['OBSOLETE']!=None self.syntax = d['SYNTAX'][0] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert self.syntax is None or type(self.syntax)==StringType return def __str__(self): @@ -448,10 +420,6 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.obsolete = d['OBSOLETE']!=None self.applies = d['APPLIES'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.applies)==TupleType return def __str__(self): @@ -515,13 +483,6 @@ def _set_attrs(self,l,d): self.must = d['MUST'] self.may = d['MAY'] self.nots = d['NOT'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.aux)==TupleType - assert type(self.must)==TupleType - assert type(self.may)==TupleType - assert type(self.nots)==TupleType return def __str__(self): @@ -582,11 +543,6 @@ def _set_attrs(self,l,d): self.obsolete = d['OBSOLETE']!=None self.form = d['FORM'][0] self.sup = d['SUP'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.form)==StringType - assert type(self.sup)==TupleType return def __str__(self): @@ -646,12 +602,6 @@ def _set_attrs(self,l,d): self.oc = d['OC'][0] self.must = d['MUST'] self.may = d['MAY'] - assert type(self.names)==TupleType - assert self.desc is None or type(self.desc)==StringType - assert type(self.obsolete)==BooleanType and (self.obsolete==0 or self.obsolete==1) - assert type(self.oc)==StringType - assert type(self.must)==TupleType - assert type(self.may)==TupleType return def __str__(self): From db60bf1238d187726c6d8c75fa6f9d36ed535cca Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 17:26:34 +0000 Subject: [PATCH 043/121] removed all dependencies on modules string and types --- CHANGES | 1 + Demo/simplebrowse.py | 5 ++--- Lib/ldap/modlist.py | 14 +++++++------- Lib/ldif.py | 6 +++--- setup.py | 9 ++++----- 5 files changed, 17 insertions(+), 18 deletions(-) diff --git a/CHANGES b/CHANGES index 32550b63..15c4d0ed 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ Modules/ * _ldap.__author__ and _ldap.__license__ also set from ldap.pkginfo Lib/ +* removed all dependencies on modules string and types * 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 diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index f8e7182a..804d12f4 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -5,7 +5,6 @@ # import ldap -import string from traceback import print_exc url = "ldap://ldap.openldap.org/" @@ -71,8 +70,8 @@ if arg == '-': lastdn,dn = dn,lastdn elif arg == '..': - dn = string.join(ldap.explode_dn(dn)[1:], ",") - dn = string.strip(dn) + dn = ldap.explode_dn(dn)[1:].join(",") + dn = dn.strip() else: try: i = int(arg) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 0d1ac409..9aff147b 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -10,7 +10,7 @@ from ldap import __version__ -import string,ldap,ldap.cidict +import ldap,ldap.cidict def list_dict(l,case_insensitive=0): @@ -31,10 +31,10 @@ def list_dict(l,case_insensitive=0): def addModlist(entry,ignore_attr_types=None): """Build modify list for call of method LDAPObject.add()""" - ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) + ignore_attr_types = list_dict(map(str.lower,(ignore_attr_types or []))) modlist = [] for attrtype in entry.keys(): - if ignore_attr_types.has_key(string.lower(attrtype)): + if ignore_attr_types.has_key(str.lower(attrtype)): # This attribute type is ignored continue # Eliminate empty attr value strings in list @@ -66,14 +66,14 @@ def modifyModlist( List of attribute type names for which comparison will be made case-insensitive """ - ignore_attr_types = list_dict(map(string.lower,(ignore_attr_types or []))) - case_ignore_attr_types = list_dict(map(string.lower,(case_ignore_attr_types or []))) + ignore_attr_types = list_dict(map(str.lower,(ignore_attr_types or []))) + case_ignore_attr_types = list_dict(map(str.lower,(case_ignore_attr_types or []))) modlist = [] attrtype_lower_map = {} for a in old_entry.keys(): - attrtype_lower_map[string.lower(a)]=a + attrtype_lower_map[str.lower(a)]=a for attrtype in new_entry.keys(): - attrtype_lower = string.lower(attrtype) + attrtype_lower = str.lower(attrtype) if ignore_attr_types.has_key(attrtype_lower): # This attribute type is ignored continue diff --git a/Lib/ldif.py b/Lib/ldif.py index c4ac4e86..8b1cd285 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -21,7 +21,7 @@ 'LDIFCopy', ] -import urlparse,urllib,base64,re,types +import urlparse,urllib,base64,re try: from cStringIO import StringIO @@ -193,9 +193,9 @@ def unparse(self,dn,record): # Start with line containing the distinguished name self._unparseAttrTypeandValue('dn',dn) # Dispatch to record type specific writers - if isinstance(record,types.DictType): + if isinstance(record,dict): self._unparseEntryRecord(record) - elif isinstance(record,types.ListType): + elif isinstance(record,list): self._unparseChangeRecord(record) else: raise ValueError('Argument record must be dictionary or list instead of %s' % (repr(record))) diff --git a/setup.py b/setup.py index 841ee553..6bbf595b 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ from distutils.core import setup, Extension from ConfigParser import ConfigParser -import sys,os,string,time +import sys,os,time sys.path.insert(0, os.path.join(os.getcwd(), 'Lib/ldap')) import pkginfo @@ -36,15 +36,14 @@ class OpenLDAP2: if cfg.has_section('_ldap'): for name in dir(LDAP_CLASS): if cfg.has_option('_ldap', name): - print name + ': ' + cfg.get('_ldap', name) - setattr(LDAP_CLASS, name, string.split(cfg.get('_ldap', name))) + setattr(LDAP_CLASS, name, cfg.get('_ldap', name).split()) for i in range(len(LDAP_CLASS.defines)): LDAP_CLASS.defines[i]=((LDAP_CLASS.defines[i],None)) for i in range(len(LDAP_CLASS.extra_files)): - destdir, origfiles = string.split(LDAP_CLASS.extra_files[i], ':') - origfileslist = string.split(origfiles, ',') + destdir, origfiles = LDAP_CLASS.extra_files[i].split(':') + origfileslist = origfiles.split(',') LDAP_CLASS.extra_files[i]=(destdir, origfileslist) #-- Let distutils/setuptools do the rest From 839a5ec93a570d48708c6e01165f3adad074ea7d Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 17:41:18 +0000 Subject: [PATCH 044/121] fixed var usage when failing in test_bad_change_records() --- Tests/t_ldif.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 4898f765..4dd1e02c 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -641,7 +641,7 @@ def test_bad_change_records(self): except ValueError, value_error: pass else: - self.fail("should have raised ValueError: %r" % ldif_str) + self.fail("should have raised ValueError: %r" % bad_ldif_string) def test_mod_increment(self): self.check_records( From 45efe4f81084da45b3787029ab0239f34f778957 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 18:25:30 +0000 Subject: [PATCH 045/121] use new-style classes in ldapurl --- Lib/ldapurl.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index fa072c9e..dc81dd92 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -68,8 +68,7 @@ def ldapUrlEscape(s): """Returns URL encoding of string s""" return quote(s).replace(',','%2C').replace('/','%2F') - -class LDAPUrlExtension: +class LDAPUrlExtension(object): """ Class for parsing and unparsing LDAP URL extensions as described in RFC 4516. @@ -192,7 +191,7 @@ def unparse(self): return ','.join([ v.unparse() for v in self.values() ]) -class LDAPUrl: +class LDAPUrl(object): """ Class for parsing and unparsing LDAP URLs as described in RFC 4516. From e61079ac49b007774ace209136b1909a8a7ef10a Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 18:44:55 +0000 Subject: [PATCH 046/121] added LDIF test with line-folded, base64-encoded attribute --- CHANGES | 1 + Tests/t_ldif.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/CHANGES b/CHANGES index 15c4d0ed..c5c3320a 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Lib/ Tests/ * scripts do not directly call SlapdTestCase.setUpClass() anymore +* added LDIF test with folded, base64-encoded attribute ---------------------------------------------------------------- Released 2.5.1 2017-11-12 diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 4dd1e02c..3da213f7 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -248,6 +248,30 @@ def test_binary2(self): ] ) + def test_big_binary(self): + self.check_records( + """ + dn: cn=x,cn=y,cn=z + attrib:: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + = + + """, + [ + ( + 'cn=x,cn=y,cn=z', + {'attrib': [500*b'\0']}, + ), + ] + ) + def test_unicode(self): self.check_records( """ From 270519e645c18f14ab628070806bf543bb199131 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 18:47:21 +0000 Subject: [PATCH 047/121] module ldif now uses functions b64encode() and b64decode() --- CHANGES | 1 + Lib/ldif.py | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index c5c3320a..aad549cc 100644 --- a/CHANGES +++ b/CHANGES @@ -16,6 +16,7 @@ Lib/ * 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() Tests/ * scripts do not directly call SlapdTestCase.setUpClass() anymore diff --git a/Lib/ldif.py b/Lib/ldif.py index 8b1cd285..e06cbd39 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -21,7 +21,10 @@ 'LDIFCopy', ] -import urlparse,urllib,base64,re +import urlparse +import urllib +import re +from base64 import b64encode, b64decode try: from cStringIO import StringIO @@ -139,7 +142,7 @@ def _unparseAttrTypeandValue(self,attr_type,attr_value): """ if self._needs_base64_encoding(attr_type,attr_value): # Encode with base64 - self._unfold_lines(':: '.join([attr_type,base64.encodestring(attr_value).replace('\n','')])) + self._unfold_lines(':: '.join([attr_type, b64encode(attr_value).replace('\n','')])) else: self._unfold_lines(': '.join([attr_type,attr_value])) return # _unparseAttrTypeandValue() @@ -277,7 +280,7 @@ def __init__( self.records_read = 0 self.changetype_counter = {}.fromkeys(CHANGE_TYPES,0) # Store some symbols for better performance - self._base64_decodestring = base64.decodestring + self._b64decode = b64decode # Read very first line try: self._last_line = self._readline() @@ -346,7 +349,7 @@ def _next_key_and_value(self): attr_value = unfolded_line[colon_pos+2:].lstrip() elif value_spec=='::': # attribute value needs base64-decoding - attr_value = self._base64_decodestring(unfolded_line[colon_pos+2:]) + attr_value = self._b64decode(unfolded_line[colon_pos+2:]) elif value_spec==':<': # fetch attribute value from URL url = unfolded_line[colon_pos+2:].strip() From 4904fbf5a5df6ee6d8b4aecdd6b25d6d48320a76 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 18:48:39 +0000 Subject: [PATCH 048/121] docstring cosmetics --- Lib/slapdtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 4381ae09..35866436 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -81,8 +81,8 @@ class SlapdObject(object): Controller class for a slapd instance, OpenLDAP's server. This class creates a temporary data store for slapd, runs it - listening on a private Unix domain socket and TCP port, and initialises it with a top-level entry and - the root user. + listening on a private Unix domain socket and TCP port, + and initialises it with a top-level entry and the root user. When a reference to an instance of this class is lost, the slapd server is shut down. From 00c2b9b378e53348e6e08d77a99fa3e59b93c523 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:03:16 +0000 Subject: [PATCH 049/121] ldapurl: eliminated use of .has_key() --- Lib/ldapurl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index dc81dd92..1fa9b1ca 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -398,10 +398,10 @@ def __repr__(self): ) def __getattr__(self,name): - if self.attr2extype.has_key(name): + if name in self.attr2extype: extype = self.attr2extype[name] if self.extensions and \ - self.extensions.has_key(extype) and \ + extype in self.extensions and \ not self.extensions[extype].exvalue is None: result = unquote(self.extensions[extype].exvalue) else: @@ -413,7 +413,7 @@ def __getattr__(self,name): return result # __getattr__() def __setattr__(self,name,value): - if self.attr2extype.has_key(name): + if name in self.attr2extype: extype = self.attr2extype[name] if value is None: # A value of None means that extension is deleted @@ -427,7 +427,7 @@ def __setattr__(self,name,value): self.__dict__[name] = value def __delattr__(self,name): - if self.attr2extype.has_key(name): + if name in self.attr2extype: extype = self.attr2extype[name] if self.extensions: try: From 1e54be25bda876bdffd5013658774a008e66dcfe Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:31:13 +0000 Subject: [PATCH 050/121] ldap.modlist: removed use of .has_key() and use set for attribute value set comparison --- Lib/ldap/modlist.py | 51 +++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 9aff147b..c2cca5d0 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -13,28 +13,12 @@ import ldap,ldap.cidict -def list_dict(l,case_insensitive=0): - """ - return a dictionary with all items of l being the keys of the dictionary - - If argument case_insensitive is non-zero ldap.cidict.cidict will be - used for case-insensitive string keys - """ - if case_insensitive: - d = ldap.cidict.cidict() - else: - d = {} - for i in l: - d[i]=None - return d - - def addModlist(entry,ignore_attr_types=None): """Build modify list for call of method LDAPObject.add()""" - ignore_attr_types = list_dict(map(str.lower,(ignore_attr_types or []))) + ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) modlist = [] for attrtype in entry.keys(): - if ignore_attr_types.has_key(str.lower(attrtype)): + if attrtype.lower() in ignore_attr_types: # This attribute type is ignored continue # Eliminate empty attr value strings in list @@ -66,20 +50,20 @@ def modifyModlist( List of attribute type names for which comparison will be made case-insensitive """ - ignore_attr_types = list_dict(map(str.lower,(ignore_attr_types or []))) - case_ignore_attr_types = list_dict(map(str.lower,(case_ignore_attr_types or []))) + ignore_attr_types = set(map(str.lower,ignore_attr_types or [])) + case_ignore_attr_types = set(map(str.lower,case_ignore_attr_types or [])) modlist = [] attrtype_lower_map = {} for a in old_entry.keys(): attrtype_lower_map[str.lower(a)]=a for attrtype in new_entry.keys(): attrtype_lower = str.lower(attrtype) - if ignore_attr_types.has_key(attrtype_lower): + if attrtype_lower in ignore_attr_types: # This attribute type is ignored continue # Filter away null-strings new_value = filter(lambda x:x!=None,new_entry[attrtype]) - if attrtype_lower_map.has_key(attrtype_lower): + 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) del attrtype_lower_map[attrtype_lower] @@ -92,20 +76,13 @@ def modifyModlist( # Replace existing attribute replace_attr_value = len(old_value)!=len(new_value) if not replace_attr_value: - case_insensitive = case_ignore_attr_types.has_key(attrtype_lower) - old_value_dict=list_dict(old_value,case_insensitive) - new_value_dict=list_dict(new_value,case_insensitive) - delete_values = [] - for v in old_value: - if not new_value_dict.has_key(v): - replace_attr_value = 1 - break - add_values = [] - if not replace_attr_value: - for v in new_value: - if not old_value_dict.has_key(v): - replace_attr_value = 1 - break + if attrtype_lower in case_ignore_attr_types: + norm_func = str.lower + else: + norm_func = None + old_value_set=set(map(norm_func,old_value)) + new_value_set=set(map(norm_func,new_value)) + replace_attr_value = new_value_set != old_value_set if replace_attr_value: modlist.append((ldap.MOD_DELETE,attrtype,None)) modlist.append((ldap.MOD_ADD,attrtype,new_value)) @@ -116,7 +93,7 @@ def modifyModlist( # Remove all attributes of old_entry which are not present # in new_entry at all for a in attrtype_lower_map.keys(): - if ignore_attr_types.has_key(a): + if a in ignore_attr_types: # This attribute type is ignored continue attrtype = attrtype_lower_map[a] From 92d40af46392cf9664bb510aa6615f1558582d86 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:32:16 +0000 Subject: [PATCH 051/121] ldap.schema.tokenizer: removed use of .has_key() --- Lib/ldap/schema/tokenizer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index ede7e216..20958c09 100644 --- a/Lib/ldap/schema/tokenizer.py +++ b/Lib/ldap/schema/tokenizer.py @@ -52,16 +52,15 @@ def extract_tokens(l,known_tokens): """ assert l[0].strip()=="(" and l[-1].strip()==")",ValueError(l) result = {} - result_has_key = result.has_key result.update(known_tokens) i = 0 l_len = len(l) while i Date: Sat, 18 Nov 2017 19:33:04 +0000 Subject: [PATCH 052/121] minor code-cleaning in ldif --- Lib/ldif.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/ldif.py b/Lib/ldif.py index e06cbd39..f68838ea 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -152,9 +152,7 @@ def _unparseEntryRecord(self,entry): entry dictionary holding an entry """ - attr_types = entry.keys()[:] - attr_types.sort() - for attr_type in attr_types: + for attr_type in sorted(entry.keys()): for attr_value in entry[attr_type]: self._unparseAttrTypeandValue(attr_type,attr_value) From 591ec60ffd521422b901945070e402cfea53c81a Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:34:51 +0000 Subject: [PATCH 053/121] removed unneeded import for UserDict from ldap.schema.subentry --- Lib/ldap/schema/subentry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index a80f238d..b5e04c29 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -8,8 +8,6 @@ from ldap.schema.models import * -from UserDict import UserDict - SCHEMA_CLASS_MAPPING = ldap.cidict.cidict() SCHEMA_ATTR_MAPPING = {} From 08ff14e277500012ebab8c705cd5c275c11f9b21 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:40:25 +0000 Subject: [PATCH 054/121] use IterableUserDict in ldap.cidict --- Lib/ldap/cidict.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index a1f8fc50..68ac6f93 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -6,19 +6,19 @@ See https://www.python-ldap.org/ for details. """ -__version__ = """$Revision: 1.15 $""" +__version__ = """$Revision: 1.16 $""" -from UserDict import UserDict +from UserDict import IterableUserDict from string import lower -class cidict(UserDict): +class cidict(IterableUserDict): """ Case-insensitive but case-respecting dictionary. """ def __init__(self,default=None): self._keys = {} - UserDict.__init__(self,{}) + IterableUserDict.__init__(self,{}) self.update(default or {}) def __getitem__(self,key): @@ -39,7 +39,7 @@ def update(self,dict): self[key] = dict[key] def has_key(self,key): - return UserDict.has_key(self,lower(key)) + return IterableUserDict.has_key(self,lower(key)) def __contains__(self,key): return self.has_key(key) From 0cd22c7cc5c23aa0c097cca97b2b1351be8ea580 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:40:47 +0000 Subject: [PATCH 055/121] avoid use of .has_key() --- Tests/t_cidict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 3f9e8e43..ac56fd3f 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -39,8 +39,8 @@ def test_cidict(self): cix_items.sort() self.assertEqual(cix_items, [('AbCDeF',123), ('xYZ',987)]) del cix["abcdEF"] - self.assertEqual(cix._keys.has_key("abcdef"), False) - self.assertEqual(cix._keys.has_key("AbCDef"), False) + self.assertEqual("abcdef" in cix, False) + self.assertEqual("AbCDef" in cix._keys, False) if __name__ == '__main__': From f6586aae8ba8b1fc5cd5b9ddd5ac6e435dc73bcb Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:53:10 +0000 Subject: [PATCH 056/121] ldap.cidict: more nits around .has_key() --- Lib/ldap/cidict.py | 14 +++++++------- Tests/t_cidict.py | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index 68ac6f93..a9e6dee9 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -6,10 +6,10 @@ See https://www.python-ldap.org/ for details. """ -__version__ = """$Revision: 1.16 $""" +__version__ = """$Revision: 1.17 $""" from UserDict import IterableUserDict -from string import lower + class cidict(IterableUserDict): """ @@ -22,15 +22,15 @@ def __init__(self,default=None): self.update(default or {}) def __getitem__(self,key): - return self.data[lower(key)] + return self.data[key.lower()] def __setitem__(self,key,value): - lower_key = lower(key) + lower_key = key.lower() self._keys[lower_key] = key self.data[lower_key] = value def __delitem__(self,key): - lower_key = lower(key) + lower_key = key.lower() del self._keys[lower_key] del self.data[lower_key] @@ -39,10 +39,10 @@ def update(self,dict): self[key] = dict[key] def has_key(self,key): - return IterableUserDict.has_key(self,lower(key)) + return key in self def __contains__(self,key): - return self.has_key(key) + return IterableUserDict.__contains__(self, key.lower()) def get(self,key,failobj=None): try: diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index ac56fd3f..c8812f28 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -41,6 +41,7 @@ def test_cidict(self): del cix["abcdEF"] self.assertEqual("abcdef" in cix, False) self.assertEqual("AbCDef" in cix._keys, False) + self.assertEqual(cix.has_key("abcdef"), False) if __name__ == '__main__': From efee495f2e6f02d0aabed0558becbce54d493609 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:56:58 +0000 Subject: [PATCH 057/121] ldap.cidict.__version__ imported from package --- Lib/ldap/cidict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index a9e6dee9..d36f8c38 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -6,7 +6,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = """$Revision: 1.17 $""" +from ldap import __version__ from UserDict import IterableUserDict From 7791f9543e98a5a9017eedc8bd75ff0856252626 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 19:57:57 +0000 Subject: [PATCH 058/121] ldap.schema: avoid .has_key(), NOT_HUMAN_READABLE_LDAP_SYNTAXES is not set() --- Lib/ldap/schema/models.py | 30 +++++++++++++++--------------- Lib/ldap/schema/subentry.py | 6 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 2e84f48c..208fe408 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -8,17 +8,17 @@ from ldap.schema.tokenizer import split_tokens,extract_tokens -NOT_HUMAN_READABLE_LDAP_SYNTAXES = { - '1.3.6.1.4.1.1466.115.121.1.4':None, # Audio - '1.3.6.1.4.1.1466.115.121.1.5':None, # Binary - '1.3.6.1.4.1.1466.115.121.1.8':None, # Certificate - '1.3.6.1.4.1.1466.115.121.1.9':None, # Certificate List - '1.3.6.1.4.1.1466.115.121.1.10':None, # Certificate Pair - '1.3.6.1.4.1.1466.115.121.1.23':None, # G3 FAX - '1.3.6.1.4.1.1466.115.121.1.28':None, # JPEG - '1.3.6.1.4.1.1466.115.121.1.40':None, # Octet String - '1.3.6.1.4.1.1466.115.121.1.49':None, # Supported Algorithm -} +NOT_HUMAN_READABLE_LDAP_SYNTAXES = set([ + '1.3.6.1.4.1.1466.115.121.1.4', # Audio + '1.3.6.1.4.1.1466.115.121.1.5', # Binary + '1.3.6.1.4.1.1466.115.121.1.8', # Certificate + '1.3.6.1.4.1.1466.115.121.1.9', # Certificate List + '1.3.6.1.4.1.1466.115.121.1.10', # Certificate Pair + '1.3.6.1.4.1.1466.115.121.1.23', # G3 FAX + '1.3.6.1.4.1.1466.115.121.1.28', # JPEG + '1.3.6.1.4.1.1466.115.121.1.40', # Octet String + '1.3.6.1.4.1.1466.115.121.1.49', # Supported Algorithm +]) class SchemaElement: @@ -325,7 +325,7 @@ def _set_attrs(self,l,d): self.desc = d['DESC'][0] self.x_subst = d['X-SUBST'][0] self.not_human_readable = \ - NOT_HUMAN_READABLE_LDAP_SYNTAXES.has_key(self.oid) or \ + self.oid in NOT_HUMAN_READABLE_LDAP_SYNTAXES or \ d['X-NOT-HUMAN-READABLE'][0]=='TRUE' self.x_binary_transfer_required = d['X-BINARY-TRANSFER-REQUIRED'][0]=='TRUE' return @@ -615,7 +615,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(UserDict.UserDict): +class Entry(UserDict.IterableUserDict): """ Schema-aware implementation of an LDAP entry class. @@ -653,7 +653,7 @@ def update(self,dict): self[key] = dict[key] def __contains__(self,key): - return self.has_key(key) + return key in self def __getitem__(self,nameoroid): return self.data[self._at2key(nameoroid)] @@ -671,7 +671,7 @@ def __delitem__(self,nameoroid): def has_key(self,nameoroid): k = self._at2key(nameoroid) - return self.data.has_key(k) + return k in self.data def get(self,nameoroid,failobj): try: diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index b5e04c29..99a0dc41 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -298,7 +298,7 @@ def get_structural_oc(self,oc_list): while struct_oc_list: oid = struct_oc_list.pop() for child_oid in oc_tree[oid]: - if struct_ocs.has_key(self.getoid(ObjectClass,child_oid)): + if self.getoid(ObjectClass,child_oid) in struct_ocs: break else: result = oid @@ -365,7 +365,7 @@ def attribute_types( object_class_oid = object_class_oids.pop(0) # Check whether the objectClass with this OID # has already been processed - if oid_cache.has_key(object_class_oid): + if object_class_oid in oid_cache: continue # Cache this OID as already being processed oid_cache[object_class_oid] = None @@ -418,7 +418,7 @@ def attribute_types( # Remove all mandantory attribute types from # optional attribute type list for a in r_may.keys(): - if r_must.has_key(a): + if a in r_must: del r_may[a] # Apply attr_type_filter to results From 1514852193ceb0db66f5290d3953873bd26f80f7 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 20:08:02 +0000 Subject: [PATCH 059/121] ldap.async: avoid using .has_key() and check result types against set() instances --- Lib/ldap/async.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 81824e47..7f3d7c3f 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -11,17 +11,16 @@ from ldap import __version__ +SEARCH_RESULT_TYPES = set([ + ldap.RES_SEARCH_ENTRY, + ldap.RES_SEARCH_RESULT, + ldap.RES_SEARCH_REFERENCE, +]) -_searchResultTypes={ - ldap.RES_SEARCH_ENTRY:None, - ldap.RES_SEARCH_RESULT:None, - ldap.RES_SEARCH_REFERENCE:None, -} - -_entryResultTypes={ - ldap.RES_SEARCH_ENTRY:None, - ldap.RES_SEARCH_RESULT:None, -} +ENTRY_RESULT_TYPES = set([ + ldap.RES_SEARCH_ENTRY, + ldap.RES_SEARCH_RESULT, +]) class WrongResultType(Exception): @@ -137,8 +136,8 @@ def processResults(self,ignoreResultsNumber=0,processResultsCount=0,timeout=-1): self._afterFirstResult = 0 if not result_list: break - if not _searchResultTypes.has_key(result_type): - raise WrongResultType(result_type,_searchResultTypes.keys()) + if result_type not in SEARCH_RESULT_TYPES: + raise WrongResultType(result_type,SEARCH_RESULT_TYPES) # Loop over list of search results for result_item in result_list: if result_counter Date: Sat, 18 Nov 2017 20:08:47 +0000 Subject: [PATCH 060/121] ldap.ldapobject: avoid using .has_key() --- Lib/ldap/ldapobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 70ecec7e..33ce36ae 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -127,13 +127,13 @@ def _ldap_call(self,func,*args,**kwargs): return result def __setattr__(self,name,value): - if self.CLASSATTR_OPTION_MAPPING.has_key(name): + if name in self.CLASSATTR_OPTION_MAPPING: self.set_option(self.CLASSATTR_OPTION_MAPPING[name],value) else: self.__dict__[name] = value def __getattr__(self,name): - if self.CLASSATTR_OPTION_MAPPING.has_key(name): + if name in self.CLASSATTR_OPTION_MAPPING: return self.get_option(self.CLASSATTR_OPTION_MAPPING[name]) elif self.__dict__.has_key(name): return self.__dict__[name] From 63fd67efd7fb19969eae228ff01ac8c373a37242 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 21:03:31 +0000 Subject: [PATCH 061/121] fixed ldap.schema.models.Entry.__contains__() --- Lib/ldap/schema/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 208fe408..8471954c 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -652,8 +652,8 @@ def update(self,dict): for key in dict.keys(): self[key] = dict[key] - def __contains__(self,key): - return key in self + def __contains__(self,nameoroid): + return self._at2key(nameoroid) in self.data def __getitem__(self,nameoroid): return self.data[self._at2key(nameoroid)] From 99912eae70333ac4a5834d9ce912e938fb88f1ee Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 21:09:31 +0000 Subject: [PATCH 062/121] ldap.modlist does not need ldap.cidict anymore --- Lib/ldap/modlist.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index c2cca5d0..1c9c1dc2 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -10,7 +10,7 @@ from ldap import __version__ -import ldap,ldap.cidict +import ldap def addModlist(entry,ignore_attr_types=None): From 6b15de53e2b3b9f9b61b13e7fa871271c3d40b02 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 21:17:18 +0000 Subject: [PATCH 063/121] removed obsolete Python compability note from modules docstrings and demo scripts --- Demo/Lib/ldap/async/ldifwriter.py | 3 --- Demo/Lib/ldap/async/sizelimit.py | 3 --- Demo/Lib/ldif/ldifcopy.py | 3 --- Demo/resiter.py | 3 --- Lib/ldap/async.py | 3 --- Lib/ldap/ldapobject.py | 12 ------------ Lib/ldap/modlist.py | 4 ---- Lib/ldap/resiter.py | 3 --- Lib/ldapurl.py | 5 ----- Lib/ldif.py | 3 --- Lib/slapdtest.py | 3 --- 11 files changed, 45 deletions(-) diff --git a/Demo/Lib/ldap/async/ldifwriter.py b/Demo/Lib/ldap/async/ldifwriter.py index 8cc4aa53..96717625 100644 --- a/Demo/Lib/ldap/async/ldifwriter.py +++ b/Demo/Lib/ldap/async/ldifwriter.py @@ -7,9 +7,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldap,ldap.async diff --git a/Demo/Lib/ldap/async/sizelimit.py b/Demo/Lib/ldap/async/sizelimit.py index 05e439b1..11ed7b68 100644 --- a/Demo/Lib/ldap/async/sizelimit.py +++ b/Demo/Lib/ldap/async/sizelimit.py @@ -8,9 +8,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldap,ldap.async diff --git a/Demo/Lib/ldif/ldifcopy.py b/Demo/Lib/ldif/ldifcopy.py index 498b857a..62cb3919 100644 --- a/Demo/Lib/ldif/ldifcopy.py +++ b/Demo/Lib/ldif/ldifcopy.py @@ -7,9 +7,6 @@ This example translates the naming context of data read from input, sanitizes some attributes, maps/removes object classes, maps/removes attributes., etc. It's far from being complete though. - -Python compability note: -Tested on Python 2.0+, should run on Python 1.5.x. """ import sys,ldif diff --git a/Demo/resiter.py b/Demo/resiter.py index 33945248..ff9fe5a1 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -3,9 +3,6 @@ written by Michael Stroeder See http://www.python-ldap.org for details. - -Python compability note: -Requires Python 2.3+ """ import ldap,ldap.resiter diff --git a/Lib/ldap/async.py b/Lib/ldap/async.py index 7f3d7c3f..0dd4940c 100644 --- a/Lib/ldap/async.py +++ b/Lib/ldap/async.py @@ -2,9 +2,6 @@ ldap.async - handle async LDAP operations See https://www.python-ldap.org/ for details. - -Python compability note: -Tested on Python 2.0+ but should run on Python 1.5.x. """ import ldap diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 33ce36ae..f4e61dab 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -2,18 +2,6 @@ ldapobject.py - wraps class _ldap.LDAPObject See https://www.python-ldap.org/ for details. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x -- LDAPObject class should be exactly the same like _ldap.LDAPObject - -Usage: -Directly imported by ldap/__init__.py. The symbols of _ldap are -overridden. - -Thread-lock: -Basically calls into the LDAP lib are serialized by the module-wide -lock self._ldap_object_lock. """ from os import strerror diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 1c9c1dc2..99e4a183 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -2,10 +2,6 @@ ldap.modlist - create add/modify modlist's See https://www.python-ldap.org/ for details. - -Python compability note: -This module is known to work with Python 2.0+ but should work -with Python 1.5.2 as well. """ from ldap import __version__ diff --git a/Lib/ldap/resiter.py b/Lib/ldap/resiter.py index d8c1368f..bb726189 100644 --- a/Lib/ldap/resiter.py +++ b/Lib/ldap/resiter.py @@ -2,9 +2,6 @@ ldap.resiter - processing LDAP results with iterators See https://www.python-ldap.org/ for details. - -Python compability note: -Requires Python 2.3+ """ diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 1fa9b1ca..bbd6929f 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -2,11 +2,6 @@ ldapurl - handling of LDAP URLs as described in RFC 4516 See https://www.python-ldap.org/ for details. - -Python compability note: -This module only works with Python 2.0+ since -1. string methods are used instead of module string and -2. list comprehensions are used. """ __version__ = '2.5.2' diff --git a/Lib/ldif.py b/Lib/ldif.py index f68838ea..86b8ac94 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -2,9 +2,6 @@ ldif - generate and parse LDIF data (see RFC 2849) See https://www.python-ldap.org/ for details. - -Python compability note: -Tested with Python 2.0+, but should work with Python 1.5.2+. """ __version__ = '2.5.2' diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 35866436..2f986722 100644 --- a/Lib/slapdtest.py +++ b/Lib/slapdtest.py @@ -3,9 +3,6 @@ slapdtest - module for spawning test instances of OpenLDAP's slapd server See https://www.python-ldap.org/ for details. - -Python compability note: -This module only works with Python 2.7.x since """ __version__ = '2.5.2' From eeb4daadfc84985d75756f57011554533903f3e8 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 22:29:18 +0000 Subject: [PATCH 064/121] added Test02_ReconnectLDAPObject.test_reconnect_get_state --- Tests/t_ldapobject.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index af83e464..ec32b16d 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -7,6 +7,7 @@ import os import unittest +import pickle from slapdtest import SlapdTestCase # Switch off processing .ldaprc or ldap.conf before importing _ldap @@ -212,6 +213,31 @@ def test_reconnect_simple_bind(self): self.server.restart() self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) + def test_reconnect_get_state(self): + l1 = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + self.assertEqual( + l1.__getstate__(), + { + '_last_bind': ( + SimpleLDAPObject.simple_bind_s, + ('cn=user1,dc=slapd-test,dc=python-ldap,dc=org', '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, + }, + ) + if __name__ == '__main__': unittest.main() From da2d43dd9af9cbcf38a1e3cf037fd0929af62516 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 22:29:39 +0000 Subject: [PATCH 065/121] added Test02_ReconnectLDAPObject.test_reconnect_get_state --- Tests/t_ldapobject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index ec32b16d..dc3221e6 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -223,7 +223,7 @@ def test_reconnect_get_state(self): { '_last_bind': ( SimpleLDAPObject.simple_bind_s, - ('cn=user1,dc=slapd-test,dc=python-ldap,dc=org', 'user1_pw'), + (bind_dn, 'user1_pw'), {} ), '_options': [(17, 3)], From 657ea2b47c958c98f3f591c1bcbe931f514a351d Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 22:34:22 +0000 Subject: [PATCH 066/121] (re)numbered test classes and methods --- Tests/t_ldapobject.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index dc3221e6..9defe7cd 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -57,7 +57,7 @@ """ -class Test01_SimpleLDAPObject(SlapdTestCase): +class Test00_SimpleLDAPObject(SlapdTestCase): """ test LDAP search operations """ @@ -66,7 +66,7 @@ class Test01_SimpleLDAPObject(SlapdTestCase): @classmethod def setUpClass(cls): - super(Test01_SimpleLDAPObject, cls).setUpClass() + super(Test00_SimpleLDAPObject, cls).setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -85,7 +85,7 @@ def setUp(self): # open local LDAP connection self._ldap_conn = self._open_ldap_conn() - def test_search_subtree(self): + def test001_search_subtree(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_SUBTREE, @@ -115,7 +115,7 @@ def test_search_subtree(self): ] ) - def test_search_onelevel(self): + def test002_search_onelevel(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_ONELEVEL, @@ -141,7 +141,7 @@ def test_search_onelevel(self): ] ) - def test_search_oneattr(self): + def test003_search_oneattr(self): result = self._ldap_conn.search_s( self.server.suffix, ldap.SCOPE_SUBTREE, @@ -154,7 +154,7 @@ def test_search_oneattr(self): [('cn=Foo4,ou=Container,'+self.server.suffix, {'cn': ['Foo4']})] ) - def test_errno107(self): + def test004_errno107(self): l = self.ldap_object_class('ldap://127.0.0.1:42') try: m = l.simple_bind_s("", "") @@ -169,7 +169,7 @@ def test_errno107(self): else: self.fail("expected SERVER_DOWN, got %r" % r) - def test_invalid_credentials(self): + def test005_invalid_credentials(self): l = self.ldap_object_class(self.server.ldap_uri) # search with invalid filter try: @@ -180,7 +180,7 @@ def test_invalid_credentials(self): else: self.fail("expected INVALID_CREDENTIALS, got %r" % r) - def test_sasl_extenal_bind_s(self): + def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() self.assertEqual(l.whoami_s(), 'dn:'+self.server.root_dn.lower()) @@ -190,14 +190,14 @@ def test_sasl_extenal_bind_s(self): self.assertEqual(l.whoami_s(), authz_id.lower()) -class Test02_ReconnectLDAPObject(Test01_SimpleLDAPObject): +class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): """ test ReconnectLDAPObject by restarting slapd """ ldap_object_class = ReconnectLDAPObject - def test_reconnect_sasl_external(self): + def test101_reconnect_sasl_external(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s() authz_id = l.whoami_s() @@ -205,7 +205,7 @@ def test_reconnect_sasl_external(self): self.server.restart() self.assertEqual(l.whoami_s(), authz_id) - def test_reconnect_simple_bind(self): + def test102_reconnect_simple_bind(self): l = self.ldap_object_class(self.server.ldapi_uri) bind_dn = 'cn=user1,'+self.server.suffix l.simple_bind_s(bind_dn, 'user1_pw') @@ -213,7 +213,7 @@ def test_reconnect_simple_bind(self): self.server.restart() self.assertEqual(l.whoami_s(), 'dn:'+bind_dn) - def test_reconnect_get_state(self): + def test103_reconnect_get_state(self): l1 = self.ldap_object_class(self.server.ldapi_uri) bind_dn = 'cn=user1,'+self.server.suffix l1.simple_bind_s(bind_dn, 'user1_pw') From be191b1ff4e53a827349f5f90443d7fb34d76352 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 23:00:18 +0000 Subject: [PATCH 067/121] added Test01_ReconnectLDAPObject.test104_reconnect_restore() --- Tests/t_ldapobject.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 9defe7cd..dfe2412f 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -222,7 +222,7 @@ def test103_reconnect_get_state(self): l1.__getstate__(), { '_last_bind': ( - SimpleLDAPObject.simple_bind_s, + 'simple_bind_s', (bind_dn, 'user1_pw'), {} ), @@ -238,6 +238,16 @@ def test103_reconnect_get_state(self): }, ) + def test104_reconnect_restore(self): + l1 = self.ldap_object_class(self.server.ldapi_uri) + bind_dn = 'cn=user1,'+self.server.suffix + l1.simple_bind_s(bind_dn, 'user1_pw') + self.assertEqual(l1.whoami_s(), 'dn:'+bind_dn) + l1_state = pickle.dumps(l1) + del l1 + l2 = pickle.loads(l1_state) + self.assertEqual(l2.whoami_s(), 'dn:'+bind_dn) + if __name__ == '__main__': unittest.main() From cdd3a41d8512a480613d95aeac945b6f8a39b238 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 23:01:50 +0000 Subject: [PATCH 068/121] fixed pickling and restoring of ReconnectLDAPObject, avoid .has_key() in ldap.ldapobject --- CHANGES | 3 +++ Lib/ldap/ldapobject.py | 32 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index aad549cc..2c280ac3 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ Released 2.5.2 2017-11-xx 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 @@ -11,12 +12,14 @@ Modules/ Lib/ * removed all dependencies on modules string and types +* removed use of .has_key() * 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 diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index f4e61dab..e374858a 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -100,7 +100,7 @@ def _ldap_call(self,func,*args,**kwargs): except LDAPError, e: exc_type,exc_value,exc_traceback = sys.exc_info() try: - if not e.args[0].has_key('info') and e.args[0].has_key('errno'): + if 'info' not in e.args[0] and 'errno' in e.args[0]: e.args[0]['info'] = strerror(e.args[0]['errno']) except IndexError: pass @@ -123,7 +123,7 @@ def __setattr__(self,name,value): def __getattr__(self,name): if name in self.CLASSATTR_OPTION_MAPPING: return self.get_option(self.CLASSATTR_OPTION_MAPPING[name]) - elif self.__dict__.has_key(name): + elif name in self.__dict__: return self.__dict__[name] else: raise AttributeError,'%s has no attribute %s' % ( @@ -807,12 +807,13 @@ class ReconnectLDAPObject(SimpleLDAPObject): application. """ - __transient_attrs__ = { - '_l':None, - '_ldap_object_lock':None, - '_trace_file':None, - '_reconnect_lock':None, - } + __transient_attrs__ = set([ + '_l', + '_ldap_object_lock', + '_trace_file', + '_reconnect_lock', + '_last_bind', + ]) def __init__( self,uri, @@ -840,15 +841,18 @@ def __init__( def __getstate__(self): """return data representation for pickled object""" - d = {} - for k,v in self.__dict__.items(): - if not self.__transient_attrs__.has_key(k): - d[k] = v - return d + state = dict([ + (k,v) + for k,v in self.__dict__.items() + if k not in self.__transient_attrs__ + ]) + state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2] + return state def __setstate__(self,d): """set up the object from pickled data""" self.__dict__.update(d) + self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2] self._ldap_object_lock = self._ldap_lock() self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) self._trace_file = sys.stdout @@ -918,7 +922,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): return # reconnect() def _apply_method_s(self,func,*args,**kwargs): - if not self.__dict__.has_key('_l'): + if not hasattr(self,'_l'): self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) try: return func(self,*args,**kwargs) From 8bded9cfd8a0fc1750c9d56a8f7abc083a47adb2 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 23:06:52 +0000 Subject: [PATCH 069/121] ldap.cidict: avoid using .has_key() --- Lib/ldap/cidict.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index d36f8c38..07832efb 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -71,7 +71,7 @@ def strlist_minus(a,b): result = [ elt for elt in a - if not temp.has_key(elt) + if elt not in temp ] return result @@ -86,7 +86,7 @@ def strlist_intersection(a,b): result = [ temp[elt] for elt in b - if temp.has_key(elt) + if elt in temp ] return result From 6615bb1b7d9441a976ad6f0f79f0e5755b51c336 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 23:33:16 +0000 Subject: [PATCH 070/121] modifyModlist(): avoid map(None, ...) --- Lib/ldap/modlist.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/modlist.py b/Lib/ldap/modlist.py index 99e4a183..6f4bf1ec 100644 --- a/Lib/ldap/modlist.py +++ b/Lib/ldap/modlist.py @@ -74,10 +74,11 @@ def modifyModlist( if not replace_attr_value: if attrtype_lower in case_ignore_attr_types: norm_func = str.lower + old_value_set = set(map(str.lower,old_value)) + new_value_set = set(map(str.lower,new_value)) else: - norm_func = None - old_value_set=set(map(norm_func,old_value)) - new_value_set=set(map(norm_func,new_value)) + old_value_set = set(old_value) + new_value_set = set(new_value) replace_attr_value = new_value_set != old_value_set if replace_attr_value: modlist.append((ldap.MOD_DELETE,attrtype,None)) From 3597f2a2fcb13ecd1a774a5cfcb534f437d437de Mon Sep 17 00:00:00 2001 From: stroeder Date: Sat, 18 Nov 2017 23:57:01 +0000 Subject: [PATCH 071/121] a bit of PEP-8 for ldap.logger --- Lib/ldap/logger.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/ldap/logger.py b/Lib/ldap/logger.py index d955c2e4..4db961e3 100644 --- a/Lib/ldap/logger.py +++ b/Lib/ldap/logger.py @@ -5,15 +5,15 @@ import logging -class logging_file_class: +class logging_file_class(object): - def __init__(self,logging_level): - self._logging_level = logging_level + def __init__(self, logging_level): + self._logging_level = logging_level - def write(self,msg): - logging.log(self._logging_level,msg[:-1]) + def write(self, msg): + logging.log(self._logging_level, msg[:-1]) - def flush(self): - return + def flush(self): + return logging_file_obj = logging_file_class(logging.DEBUG) From 4944d9980d34db889a74f842d4897fe276849649 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 00:01:13 +0000 Subject: [PATCH 072/121] more obsolete docstring content removed --- Lib/ldap/dn.py | 3 --- Lib/ldap/functions.py | 12 ------------ Lib/ldap/sasl.py | 3 --- 3 files changed, 18 deletions(-) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index daab8ab7..1d700584 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -2,9 +2,6 @@ dn.py - misc stuff for handling distinguished names (see RFC 4514) See https://www.python-ldap.org/ for details. - -Compability: -- Tested with Python 2.0+ """ from ldap.pkginfo import __version__ diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index ea763fac..6ddab54c 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -2,18 +2,6 @@ functions.py - wraps functions of module _ldap See https://www.python-ldap.org/ for details. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x -- functions should behave exactly the same like in _ldap - -Usage: -Directly imported by ldap/__init__.py. The symbols of _ldap are -overridden. - -Thread-lock: -Basically calls into the LDAP lib are serialized by the module-wide -lock _ldapmodule_lock. """ from ldap import __version__ diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index 81438ccb..77d8ee20 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -10,9 +10,6 @@ LDAPObject's sasl_bind_s() method Implementing support for new sasl mechanism is very easy --- see the examples of digest_md5 and gssapi. - -Compability: -- Tested with Python 2.0+ but should work with Python 1.5.x """ from ldap import __version__ From e8eb3eeb9cd48a0be785fa53d7d1e2a91a3b5935 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 00:10:42 +0000 Subject: [PATCH 073/121] a bit of PEP-8 for ldap.sasl --- Lib/ldap/sasl.py | 71 +++++++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index 77d8ee20..d67153dc 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -15,18 +15,19 @@ from ldap import __version__ if __debug__: - # Tracing is only supported in debugging mode - import traceback - from ldap import _trace_level,_trace_file,_trace_stack_limit + # Tracing is only supported in debugging mode + from ldap import _trace_level, _trace_file + # These are the SASL callback id's , as defined in sasl.h -CB_USER = 0x4001 -CB_AUTHNAME = 0x4002 -CB_LANGUAGE = 0x4003 -CB_PASS = 0x4004 -CB_ECHOPROMPT = 0x4005 -CB_NOECHOPROMPT= 0x4006 -CB_GETREALM = 0x4008 +CB_USER = 0x4001 +CB_AUTHNAME = 0x4002 +CB_LANGUAGE = 0x4003 +CB_PASS = 0x4004 +CB_ECHOPROMPT = 0x4005 +CB_NOECHOPROMPT = 0x4006 +CB_GETREALM = 0x4008 + class sasl: """This class handles SASL interactions for authentication. @@ -35,7 +36,7 @@ class sasl: specific SASL authentication mechanisms, this method can be overridden""" - def __init__(self,cb_value_dict,mech): + def __init__(self, cb_value_dict, mech): """ The (generic) base class takes a cb_value_dictionary of question-answer pairs. Questions are specified by the respective SASL callback id's. The mech argument is a string that specifies @@ -43,7 +44,7 @@ def __init__(self,cb_value_dict,mech): self.cb_value_dict = cb_value_dict or {} self.mech = mech - def callback(self,cb_id,challenge,prompt,defresult): + def callback(self, cb_id, challenge, prompt, defresult): """ The callback method will be called by the sasl_bind_s() method several times. Each time it will provide the id, which tells us what kind of information is requested (the CB_ ... @@ -61,46 +62,54 @@ def callback(self,cb_id,challenge,prompt,defresult): # The following print command might be useful for debugging # new sasl mechanisms. So it is left here - cb_result = self.cb_value_dict.get(cb_id,defresult) or '' + cb_result = self.cb_value_dict.get(cb_id, defresult) or '' if __debug__: - if _trace_level>=1: - _trace_file.write("*** id=%d, challenge=%s, prompt=%s, defresult=%s\n-> %s\n" % ( - cb_id, challenge, prompt, repr(defresult), repr(self.cb_value_dict.get(cb_result)) - )) + if _trace_level >= 1: + _trace_file.write("*** id=%d, challenge=%s, prompt=%s, defresult=%s\n-> %s\n" % ( + cb_id, + challenge, + prompt, + repr(defresult), + repr(self.cb_value_dict.get(cb_result)) + )) return cb_result class cram_md5(sasl): """This class handles SASL CRAM-MD5 authentication.""" - def __init__(self,authc_id, password, authz_id=""): - auth_dict = {CB_AUTHNAME:authc_id, CB_PASS:password, - CB_USER:authz_id} - sasl.__init__(self,auth_dict,"CRAM-MD5") + def __init__(self, authc_id, password, authz_id=""): + auth_dict = { + CB_AUTHNAME: authc_id, + CB_PASS: password, + CB_USER: authz_id, + } + sasl.__init__(self, auth_dict, "CRAM-MD5") class digest_md5(sasl): """This class handles SASL DIGEST-MD5 authentication.""" - def __init__(self,authc_id, password, authz_id=""): - auth_dict = {CB_AUTHNAME:authc_id, CB_PASS:password, - CB_USER:authz_id} - sasl.__init__(self,auth_dict,"DIGEST-MD5") + def __init__(self, authc_id, password, authz_id=""): + auth_dict = { + CB_AUTHNAME: authc_id, + CB_PASS: password, + CB_USER: authz_id, + } + sasl.__init__(self, auth_dict, "DIGEST-MD5") class gssapi(sasl): """This class handles SASL GSSAPI (i.e. Kerberos V) authentication.""" - def __init__(self,authz_id=""): - sasl.__init__(self, {CB_USER:authz_id},"GSSAPI") + def __init__(self, authz_id=""): + sasl.__init__(self, {CB_USER: authz_id}, "GSSAPI") class external(sasl): """This class handles SASL EXTERNAL authentication (i.e. X.509 client certificate)""" - def __init__(self,authz_id=""): - sasl.__init__(self, {CB_USER:authz_id},"EXTERNAL") - - + def __init__(self, authz_id=""): + sasl.__init__(self, {CB_USER: authz_id}, "EXTERNAL") From 1c23992b1b8721f4922611bb266ca8ea11ecf331 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 00:12:30 +0000 Subject: [PATCH 074/121] docstring line-wrapping --- Lib/ldap/sasl.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/Lib/ldap/sasl.py b/Lib/ldap/sasl.py index d67153dc..34d4cb04 100644 --- a/Lib/ldap/sasl.py +++ b/Lib/ldap/sasl.py @@ -30,22 +30,27 @@ class sasl: - """This class handles SASL interactions for authentication. + """ + This class handles SASL interactions for authentication. If an instance of this class is passed to ldap's sasl_bind_s() method, the library will call its callback() method. For specific SASL authentication mechanisms, this method can be - overridden""" + overridden + """ def __init__(self, cb_value_dict, mech): - """ The (generic) base class takes a cb_value_dictionary of + """ + The (generic) base class takes a cb_value_dictionary of question-answer pairs. Questions are specified by the respective SASL callback id's. The mech argument is a string that specifies - the SASL mechaninsm to be uesd.""" + the SASL mechaninsm to be uesd. + """ self.cb_value_dict = cb_value_dict or {} self.mech = mech def callback(self, cb_id, challenge, prompt, defresult): - """ The callback method will be called by the sasl_bind_s() + """ + The callback method will be called by the sasl_bind_s() method several times. Each time it will provide the id, which tells us what kind of information is requested (the CB_ ... constants above). The challenge might be a short (english) text @@ -58,7 +63,8 @@ def callback(self, cb_id, challenge, prompt, defresult): cb_value_dictionary. Note that the current callback interface is not very useful for writing generic sasl GUIs, which would need to know all the questions to ask, before the answers are returned to the sasl - lib (in contrast to one question at a time).""" + lib (in contrast to one question at a time). + """ # The following print command might be useful for debugging # new sasl mechanisms. So it is left here @@ -76,7 +82,9 @@ def callback(self, cb_id, challenge, prompt, defresult): class cram_md5(sasl): - """This class handles SASL CRAM-MD5 authentication.""" + """ + This class handles SASL CRAM-MD5 authentication. + """ def __init__(self, authc_id, password, authz_id=""): auth_dict = { @@ -88,7 +96,9 @@ def __init__(self, authc_id, password, authz_id=""): class digest_md5(sasl): - """This class handles SASL DIGEST-MD5 authentication.""" + """ + This class handles SASL DIGEST-MD5 authentication. + """ def __init__(self, authc_id, password, authz_id=""): auth_dict = { @@ -100,16 +110,19 @@ def __init__(self, authc_id, password, authz_id=""): class gssapi(sasl): - """This class handles SASL GSSAPI (i.e. Kerberos V) - authentication.""" + """ + This class handles SASL GSSAPI (i.e. Kerberos V) authentication. + """ def __init__(self, authz_id=""): sasl.__init__(self, {CB_USER: authz_id}, "GSSAPI") class external(sasl): - """This class handles SASL EXTERNAL authentication - (i.e. X.509 client certificate)""" + """ + This class handles SASL EXTERNAL authentication + (i.e. X.509 client certificate) + """ def __init__(self, authz_id=""): sasl.__init__(self, {CB_USER: authz_id}, "EXTERNAL") From 9935cb8504964ee1d6bcb4b6dd9ed85335d3453a Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 13:52:04 +0000 Subject: [PATCH 075/121] added more tests for sub-module ldap.dn --- CHANGES | 1 + Tests/t_ldap_dn.py | 201 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/CHANGES b/CHANGES index 2c280ac3..7aaef0f8 100644 --- a/CHANGES +++ b/CHANGES @@ -24,6 +24,7 @@ Lib/ 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 ---------------------------------------------------------------- Released 2.5.1 2017-11-12 diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 043cc702..9a3c54a7 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -34,5 +34,206 @@ def test_is_dn(self): True ) + def test_escape_dn_chars(self): + """ + test function escape_dn_chars() + """ + self.assertEqual(ldap.dn.escape_dn_chars('foobar'), 'foobar') + self.assertEqual(ldap.dn.escape_dn_chars('foo,bar'), 'foo\\,bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo=bar'), 'foo\\=bar') + self.assertEqual(ldap.dn.escape_dn_chars('foo#bar'), 'foo#bar') + self.assertEqual(ldap.dn.escape_dn_chars('#foobar'), '\\#foobar') + self.assertEqual(ldap.dn.escape_dn_chars('foo bar'), 'foo bar') + self.assertEqual(ldap.dn.escape_dn_chars(' foobar'), '\\ foobar') + + def test_str2dn(self): + """ + test function str2dn() + """ + self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual( + ldap.dn.str2dn('uid=test42,ou=Testing,dc=example,dc=com'), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test42+uidNumber=42,ou=Testing,dc=example,dc=com'), + [ + [('uid', 'test42', 1), ('uidNumber', '42', 1) ], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test42,ou=Testing,dc=example,dc=com', flags=0), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('uid=test\\, 42,ou=Testing,dc=example,dc=com', flags=0), + [ + [('uid', 'test, 42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('cn=\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', 4)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + self.assertEqual( + ldap.dn.str2dn('cn=\\c3\\a4\\c3\\b6\\c3\\bc\\c3\\84\\c3\\96\\c3\\9c\\c3\\9f,dc=example,dc=com', flags=0), + [ + [('cn', '\xc3\xa4\xc3\xb6\xc3\xbc\xc3\x84\xc3\x96\xc3\x9c\xc3\x9f', 4)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) + + def test_dn2str(self): + """ + test function dn2str() + """ + self.assertEqual(ldap.dn.str2dn(''), []) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42,ou=Testing,dc=example,dc=com', + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test42', 1), ('uidNumber', '42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test42+uidNumber=42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('uid', 'test, 42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ]), + 'uid=test\\, 42,ou=Testing,dc=example,dc=com' + ) + self.assertEqual( + ldap.dn.dn2str([ + [('cn', '\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)], + [('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' + ) + + def test_explode_dn(self): + """ + test function explode_dn() + """ + self.assertEqual(ldap.dn.explode_dn(''), []) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com'), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', flags=0), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', notypes=True), + ['test42', 'Testing', 'example', 'com'] + ) + self.assertEqual( + ldap.dn.explode_dn('uid=test\\, 42,ou=Testing,dc=example,dc=com', flags=0), + ['uid=test\\, 42', 'ou=Testing', 'dc=example', 'dc=com'] + ) + self.assertEqual( + ldap.dn.explode_dn('cn=\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'] + ) + 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'] + ) + + def test_explode_rdn(self): + """ + test function explode_rdn() + """ + self.assertEqual(ldap.dn.explode_rdn(''), []) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42'), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=False, flags=0), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=0, flags=0), + ['uid=test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42+uidNumber=42', flags=0), + ['uid=test42', 'uidNumber=42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=True), + ['test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test42', notypes=1), + ['test42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('uid=test\\+ 42', flags=0), + ['uid=test\\+ 42'] + ) + self.assertEqual( + ldap.dn.explode_rdn('cn=\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'] + ) + 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'] + ) + + if __name__ == '__main__': unittest.main() From 389725160295b4a8844dabf833996f4ae3bbde4a Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 15:52:43 +0000 Subject: [PATCH 076/121] ldap.dn: use Boolean for notypes --- Doc/ldap-dn.rst | 4 ++-- Lib/ldap/dn.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/ldap-dn.rst b/Doc/ldap-dn.rst index d78f070c..d896e4cf 100644 --- a/Doc/ldap-dn.rst +++ b/Doc/ldap-dn.rst @@ -49,7 +49,7 @@ The :mod:`ldap.dn` module defines the following functions: function :func:`escape_dn_chars`. -.. function:: explode_dn(dn [, notypes=0[, flags=0]]) -> list +.. function:: explode_dn(dn [, notypes=False[, flags=0]]) -> list This function takes *dn* and breaks it up into its component parts. Each part is known as an RDN (Relative Distinguished Name). The optional *notypes* @@ -60,7 +60,7 @@ The :mod:`ldap.dn` module defines the following functions: deprecated. -.. function:: explode_rdn(rdn [, notypes=0[, flags=0]]) -> list +.. function:: explode_rdn(rdn [, notypes=False[, flags=0]]) -> list This function takes a (multi-valued) *rdn* and breaks it up into a list of characteristic attributes. The optional *notypes* parameter is used to specify diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 1d700584..32985510 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -62,9 +62,9 @@ def dn2str(dn): for rdn in dn ]) -def explode_dn(dn,notypes=0,flags=0): +def explode_dn(dn, notypes=False, flags=0): """ - explode_dn(dn [, notypes=0]) -> list + explode_dn(dn [, notypes=False [, flags=0]]) -> list This function takes a DN and breaks it up into its component parts. The notypes parameter is used to specify that only the component's @@ -88,9 +88,9 @@ def explode_dn(dn,notypes=0,flags=0): return rdn_list -def explode_rdn(rdn,notypes=0,flags=0): +def explode_rdn(rdn, notypes=False, flags=0): """ - explode_rdn(rdn [, notypes=0]) -> list + explode_rdn(rdn [, notypes=0 [, flags=0]]) -> list This function takes a RDN and breaks it up into its component parts if it is a multi-valued RDN. From 5bdd161747ca2be71c1cea8a78d13a1f3a381e81 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 16:39:55 +0000 Subject: [PATCH 077/121] use example.com in examples and tests --- Doc/ldap-dn.rst | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/ldap-dn.rst b/Doc/ldap-dn.rst index d896e4cf..c22a64c4 100644 --- a/Doc/ldap-dn.rst +++ b/Doc/ldap-dn.rst @@ -85,26 +85,26 @@ Splitting a LDAPv3 DN to AVA level. Note that both examples have the same result but in the first example the non-ASCII chars are passed as is (byte buffer string) whereas in the second example the hex-encoded DN representation are passed to the function. ->>> ldap.dn.str2dn('cn=Michael Str\xc3\xb6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3) -[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] ->>> ldap.dn.str2dn('cn=Michael Str\C3\B6der,dc=stroeder,dc=com',flags=ldap.DN_FORMAT_LDAPV3) -[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=Michael Str\xc3\xb6der,dc=example,dc=com',flags=ldap.DN_FORMAT_LDAPV3) +[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=Michael Str\C3\B6der,dc=example,dc=com',flags=ldap.DN_FORMAT_LDAPV3) +[[('cn', 'Michael Str\xc3\xb6der', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]] Splitting a LDAPv2 DN into RDN parts: ->>> ldap.dn.explode_dn('cn=Michael Stroeder;dc=stroeder;dc=com',flags=ldap.DN_FORMAT_LDAPV2) -['cn=Michael Stroeder', 'dc=stroeder', 'dc=com'] +>>> ldap.dn.explode_dn('cn=John Doe;dc=example;dc=com',flags=ldap.DN_FORMAT_LDAPV2) +['cn=John Doe', 'dc=example', 'dc=com'] Splitting a multi-valued RDN: ->>> ldap.dn.explode_rdn('cn=Michael Stroeder+mail=michael@stroeder.com',flags=ldap.DN_FORMAT_LDAPV2) -['cn=Michael Stroeder', 'mail=michael@stroeder.com'] +>>> ldap.dn.explode_rdn('cn=John Doe+mail=john.doe@example.com',flags=ldap.DN_FORMAT_LDAPV2) +['cn=John Doe', 'mail=john.doe@example.com'] Splitting a LDAPv3 DN with a multi-valued RDN into its AVA parts: ->>> ldap.dn.str2dn('cn=Michael Stroeder+mail=michael@stroeder.com,dc=stroeder,dc=com') -[[('cn', 'Michael Stroeder', 1), ('mail', 'michael@stroeder.com', 1)], [('dc', 'stroeder', 1)], [('dc', 'com', 1)]] +>>> ldap.dn.str2dn('cn=John Doe+mail=john.doe@example.com,dc=example,dc=com') +[[('cn', 'John Doe', 1), ('mail', 'john.doe@example.com', 1)], [('dc', 'example', 1)], [('dc', 'com', 1)]] From 4a9591ca1ef4f93bcd2a3d4429969d24c766d428 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 16:50:18 +0000 Subject: [PATCH 078/121] removed class ldap.ldapobject.NonblockingLDAPObject --- CHANGES | 1 + Lib/ldap/ldapobject.py | 37 +------------------------------------ 2 files changed, 2 insertions(+), 36 deletions(-) diff --git a/CHANGES b/CHANGES index 7aaef0f8..1d3bba02 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Modules/ 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 diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index e374858a..855dabff 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -6,12 +6,11 @@ from os import strerror -from ldap import __version__ +from ldap.pkginfo import __version__, __author__, __license__ __all__ = [ 'LDAPObject', 'SimpleLDAPObject', - 'NonblockingLDAPObject', 'ReconnectLDAPObject', ] @@ -760,40 +759,6 @@ def get_naming_contexts(self): ).get('namingContexts', []) -class NonblockingLDAPObject(SimpleLDAPObject): - - def __init__(self,uri,trace_level=0,trace_file=None,result_timeout=-1): - self._result_timeout = result_timeout - SimpleLDAPObject.__init__(self,uri,trace_level,trace_file) - - def result(self,msgid=ldap.RES_ANY,all=1,timeout=-1): - """ - """ - ldap_result = self._ldap_call(self._l.result,msgid,0,self._result_timeout) - if not all: - return ldap_result - start_time = time.time() - all_results = [] - while all: - while ldap_result[0] is None: - if (timeout>=0) and (time.time()-start_time>timeout): - self._ldap_call(self._l.abandon,msgid) - raise ldap.TIMEOUT( - "LDAP time limit (%d secs) exceeded." % (timeout) - ) - time.sleep(0.00001) - ldap_result = self._ldap_call(self._l.result,msgid,0,self._result_timeout) - if ldap_result[1] is None: - break - all_results.extend(ldap_result[1]) - ldap_result = None,None - return all_results - - def search_st(self,base,scope,filterstr='(objectClass=*)',attrlist=None,attrsonly=0,timeout=-1): - msgid = self.search(base,scope,filterstr,attrlist,attrsonly) - return self.result(msgid,all=1,timeout=timeout) - - class ReconnectLDAPObject(SimpleLDAPObject): """ In case of server failure (ldap.SERVER_DOWN) the implementations From 0a1e520048db007b31515de0941c26fd0673abed Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 18:00:49 +0000 Subject: [PATCH 079/121] added ldap.dn tests with ldap.DN_FORMAT_LDAPV2 --- Tests/t_ldap_dn.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index 9a3c54a7..97274f37 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -78,6 +78,15 @@ def test_str2dn(self): [('dc', 'com', 1)] ] ) + self.assertEqual( + ldap.dn.str2dn('uid=test42; ou=Testing; dc=example; dc=com', flags=ldap.DN_FORMAT_LDAPV2), + [ + [('uid', 'test42', 1)], + [('ou', 'Testing', 1)], + [('dc', 'example', 1)], + [('dc', 'com', 1)] + ] + ) self.assertEqual( ldap.dn.str2dn('uid=test\\, 42,ou=Testing,dc=example,dc=com', flags=0), [ @@ -175,6 +184,10 @@ def test_explode_dn(self): ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', flags=0), ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] ) + self.assertEqual( + ldap.dn.explode_dn('uid=test42; ou=Testing; dc=example; dc=com', flags=ldap.DN_FORMAT_LDAPV2), + ['uid=test42', 'ou=Testing', 'dc=example', 'dc=com'] + ) self.assertEqual( ldap.dn.explode_dn('uid=test42,ou=Testing,dc=example,dc=com', notypes=True), ['test42', 'Testing', 'example', 'com'] From 101d2e21fe7144b517f3232b3a33239d00517bf3 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 18:02:05 +0000 Subject: [PATCH 080/121] bumped Doc version to 2.5.2.0 --- Doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index ab7c509e..9c322de3 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -42,7 +42,7 @@ # The short X.Y version. version = '2.5' # The full version, including alpha/beta/rc tags. -release = '2.5.1.0' +release = '2.5.2.0' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From a0e459fc3231dbdf8f3cd2db6e89b373e7cecdd2 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 18:54:28 +0000 Subject: [PATCH 081/121] ldap.resiter: PEP-8 and a small fix --- Lib/ldap/resiter.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/Lib/ldap/resiter.py b/Lib/ldap/resiter.py index bb726189..dc912eb3 100644 --- a/Lib/ldap/resiter.py +++ b/Lib/ldap/resiter.py @@ -4,21 +4,38 @@ See https://www.python-ldap.org/ for details. """ +from ldap.pkginfo import __version__, __author__, __license__ -class ResultProcessor: - """ - Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes. - """ - def allresults(self,msgid,timeout=-1,add_ctrls=0): +class ResultProcessor: """ - Generator function which returns an iterator for processing all LDAP operation - results of the given msgid retrieved with LDAPObject.result3() -> 4-tuple + Mix-in class used with ldap.ldapopbject.LDAPObject or derived classes. """ - result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls) - while result_type and result_list: - # Loop over list of search results - for result_item in result_list: - yield (result_type,result_list,result_msgid,result_serverctrls) - result_type,result_list,result_msgid,result_serverctrls,_,_ = self.result4(msgid,0,timeout,add_ctrls=add_ctrls) - return # allresults() + + def allresults(self, msgid, timeout=-1, add_ctrls=0): + """ + Generator function which returns an iterator for processing all LDAP operation + results of the given msgid like retrieved with LDAPObject.result3() -> 4-tuple + """ + result_type, result_list, result_msgid, result_serverctrls, _, _ = \ + self.result4( + msgid, + 0, + timeout, + add_ctrls=add_ctrls + ) + while result_type and result_list: + yield ( + result_type, + result_list, + result_msgid, + result_serverctrls + ) + result_type, result_list, result_msgid, result_serverctrls, _, _ = \ + self.result4( + msgid, + 0, + timeout, + add_ctrls=add_ctrls + ) + return # allresults() From 5bf5f3cbb966214ed6f71335210654bc3d06e6c7 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 20:23:51 +0000 Subject: [PATCH 082/121] some code cosmetics in ldap.syncrepl and also a small fix --- Lib/ldap/syncrepl.py | 361 ++++++++++++++++++++++++------------------- 1 file changed, 204 insertions(+), 157 deletions(-) diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 93a3a2af..8442726a 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -5,70 +5,79 @@ See https://www.python-ldap.org/ for project details. """ -#__all__ = [ -# '', -# '', -#] - from uuid import UUID -# Imports from python-ldap 2.4+ -import ldap.ldapobject -from ldap.controls import RequestControl,ResponseControl,KNOWN_RESPONSE_CONTROLS - # Imports from pyasn1 -from pyasn1.type import tag,namedtype,namedval,univ,constraint -from pyasn1.codec.ber import encoder,decoder - -__all__ = [ 'SyncreplConsumer' ] - -# RFC 4533: -# -# syncUUID ::= OCTET STRING (SIZE(16)) -# syncCookie ::= OCTET STRING - -class syncUUID(univ.OctetString): - subtypeSpec = constraint.ValueSizeConstraint(16,16) - -class syncCookie(univ.OctetString): - pass - -# 2.2. Sync Request Control -# -# The Sync Request Control is an LDAP Control [RFC4511] where the -# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.1 and the -# controlValue, an OCTET STRING, contains a BER-encoded -# syncRequestValue. The criticality field is either TRUE or FALSE. -# -# syncRequestValue ::= SEQUENCE { -# mode ENUMERATED { -# -- 0 unused -# refreshOnly (1), -# -- 2 reserved -# refreshAndPersist (3) -# }, -# cookie syncCookie OPTIONAL, -# reloadHint BOOLEAN DEFAULT FALSE -# } -# -# The Sync Request Control is only applicable to the SearchRequest -# Message. - -class syncRequestMode(univ.Enumerated): +from pyasn1.type import tag, namedtype, namedval, univ, constraint +from pyasn1.codec.ber import encoder, decoder + +from ldap.pkginfo import __version__, __author__, __license__ +from ldap.controls import RequestControl, ResponseControl, KNOWN_RESPONSE_CONTROLS + +__all__ = [ + 'SyncreplConsumer', +] + + +class SyncUUID(univ.OctetString): + """ + syncUUID ::= OCTET STRING (SIZE(16)) + """ + subtypeSpec = constraint.ValueSizeConstraint(16, 16) + + +class SyncCookie(univ.OctetString): + """ + syncCookie ::= OCTET STRING + """ + + +class SyncRequestMode(univ.Enumerated): + """ + mode ENUMERATED { + -- 0 unused + refreshOnly (1), + -- 2 reserved + refreshAndPersist (3) + }, + """ namedValues = namedval.NamedValues( ('refreshOnly', 1), ('refreshAndPersist', 3) ) - subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1,3) + subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(1, 3) -class syncRequestValue(univ.Sequence): + +class SyncRequestValue(univ.Sequence): + """ + syncRequestValue ::= SEQUENCE { + mode ENUMERATED { + -- 0 unused + refreshOnly (1), + -- 2 reserved + refreshAndPersist (3) + }, + cookie syncCookie OPTIONAL, + reloadHint BOOLEAN DEFAULT FALSE + } + """ componentType = namedtype.NamedTypes( - namedtype.NamedType('mode', syncRequestMode()), - namedtype.OptionalNamedType('cookie', syncCookie()), + namedtype.NamedType('mode', SyncRequestMode()), + namedtype.OptionalNamedType('cookie', SyncCookie()), namedtype.DefaultedNamedType('reloadHint', univ.Boolean(False)) ) + class SyncRequestControl(RequestControl): + """ + The Sync Request Control is an LDAP Control [RFC4511] where the + controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.1 and the + controlValue, an OCTET STRING, contains a BER-encoded + syncRequestValue. The criticality field is either TRUE or FALSE. + [..] + The Sync Request Control is only applicable to the SearchRequest + Message. + """ controlType = '1.3.6.1.4.1.4203.1.9.1.1' def __init__(self, criticality=1, cookie=None, mode='refreshOnly', reloadHint=False): @@ -78,62 +87,73 @@ def __init__(self, criticality=1, cookie=None, mode='refreshOnly', reloadHint=Fa self.reloadHint = reloadHint def encodeControlValue(self): - r = syncRequestValue() - r.setComponentByName('mode', syncRequestMode(self.mode)) + rcv = SyncRequestValue() + rcv.setComponentByName('mode', SyncRequestMode(self.mode)) if self.cookie is not None: - r.setComponentByName('cookie', syncCookie(self.cookie)) + rcv.setComponentByName('cookie', SyncCookie(self.cookie)) if self.reloadHint: - r.setComponentbyName('reloadHint', univ.Boolean(self.reloadHint)) - return encoder.encode(r) - -# 2.3. Sync State Control -# -# The Sync State Control is an LDAP Control [RFC4511] where the -# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.2 and the -# controlValue, an OCTET STRING, contains a BER-encoded syncStateValue. -# The criticality is FALSE. -# -# syncStateValue ::= SEQUENCE { -# state ENUMERATED { -# present (0), -# add (1), -# modify (2), -# delete (3) -# }, -# entryUUID syncUUID, -# cookie syncCookie OPTIONAL -# } -# -# The Sync State Control is only applicable to SearchResultEntry and -# SearchResultReference Messages. - -class syncStateOp(univ.Enumerated): + rcv.setComponentByName('reloadHint', univ.Boolean(self.reloadHint)) + return encoder.encode(rcv) + + +class SyncStateOp(univ.Enumerated): + """ + state ENUMERATED { + present (0), + add (1), + modify (2), + delete (3) + }, + """ namedValues = namedval.NamedValues( ('present', 0), ('add', 1), ('modify', 2), ('delete', 3) ) - subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3) + subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0, 1, 2, 3) -class syncStateValue(univ.Sequence): + +class SyncStateValue(univ.Sequence): + """ + syncStateValue ::= SEQUENCE { + state ENUMERATED { + present (0), + add (1), + modify (2), + delete (3) + }, + entryUUID syncUUID, + cookie syncCookie OPTIONAL + } + """ componentType = namedtype.NamedTypes( - namedtype.NamedType('state', syncStateOp()), - namedtype.NamedType('entryUUID', syncUUID()), - namedtype.OptionalNamedType('cookie', syncCookie()) + namedtype.NamedType('state', SyncStateOp()), + namedtype.NamedType('entryUUID', SyncUUID()), + namedtype.OptionalNamedType('cookie', SyncCookie()) ) + class SyncStateControl(ResponseControl): + """ + The Sync State Control is an LDAP Control [RFC4511] where the + controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.2 and the + controlValue, an OCTET STRING, contains a BER-encoded SyncStateValue. + The criticality is FALSE. + [..] + The Sync State Control is only applicable to SearchResultEntry and + SearchResultReference Messages. + """ controlType = '1.3.6.1.4.1.4203.1.9.1.2' - opnames = ( 'present', 'add', 'modify', 'delete' ) + opnames = ('present', 'add', 'modify', 'delete') def decodeControlValue(self, encodedControlValue): - d = decoder.decode(encodedControlValue, asn1Spec = syncStateValue()) + d = decoder.decode(encodedControlValue, asn1Spec=SyncStateValue()) state = d[0].getComponentByName('state') uuid = UUID(bytes=bytes(d[0].getComponentByName('entryUUID'))) cookie = d[0].getComponentByName('cookie') - if cookie.hasValue(): - self.cookie = str(self.cookie) + if cookie is not None and cookie.hasValue(): + self.cookie = str(cookie) else: self.cookie = None self.state = self.__class__.opnames[int(state)] @@ -141,32 +161,34 @@ def decodeControlValue(self, encodedControlValue): KNOWN_RESPONSE_CONTROLS[SyncStateControl.controlType] = SyncStateControl -# 2.4. Sync Done Control -# -# The Sync Done Control is an LDAP Control [RFC4511] where the -# controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.3 and the -# controlValue contains a BER-encoded syncDoneValue. The criticality -# is FALSE (and hence absent). -# -# syncDoneValue ::= SEQUENCE { -# cookie syncCookie OPTIONAL, -# refreshDeletes BOOLEAN DEFAULT FALSE -# } -# -# The Sync Done Control is only applicable to the SearchResultDone -# Message. - -class syncDoneValue(univ.Sequence): + +class SyncDoneValue(univ.Sequence): + """ + syncDoneValue ::= SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDeletes BOOLEAN DEFAULT FALSE + } + """ componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('cookie', syncCookie()), + namedtype.OptionalNamedType('cookie', SyncCookie()), namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False)) ) + class SyncDoneControl(ResponseControl): + """ + The Sync Done Control is an LDAP Control [RFC4511] where the + controlType is the object identifier 1.3.6.1.4.1.4203.1.9.1.3 and the + controlValue contains a BER-encoded syncDoneValue. The criticality + is FALSE (and hence absent). + [..] + The Sync Done Control is only applicable to the SearchResultDone + Message. + """ controlType = '1.3.6.1.4.1.4203.1.9.1.3' def decodeControlValue(self, encodedControlValue): - d = decoder.decode(encodedControlValue, asn1Spec = syncDoneValue()) + d = decoder.decode(encodedControlValue, asn1Spec=SyncDoneValue()) cookie = d[0].getComponentByName('cookie') if cookie.hasValue(): self.cookie = str(cookie) @@ -181,95 +203,121 @@ def decodeControlValue(self, encodedControlValue): KNOWN_RESPONSE_CONTROLS[SyncDoneControl.controlType] = SyncDoneControl -# 2.5. Sync Info Message -# -# The Sync Info Message is an LDAP Intermediate Response Message -# [RFC4511] where responseName is the object identifier -# 1.3.6.1.4.1.4203.1.9.1.4 and responseValue contains a BER-encoded -# syncInfoValue. The criticality is FALSE (and hence absent). -# -# syncInfoValue ::= CHOICE { -# newcookie [0] syncCookie, -# refreshDelete [1] SEQUENCE { -# cookie syncCookie OPTIONAL, -# refreshDone BOOLEAN DEFAULT TRUE -# }, -# refreshPresent [2] SEQUENCE { -# cookie syncCookie OPTIONAL, -# refreshDone BOOLEAN DEFAULT TRUE -# }, -# syncIdSet [3] SEQUENCE { -# cookie syncCookie OPTIONAL, -# refreshDeletes BOOLEAN DEFAULT FALSE, -# syncUUIDs SET OF syncUUID -# } -# } -# - -class refreshDelete(univ.Sequence): +class RefreshDelete(univ.Sequence): + """ + refreshDelete [1] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDone BOOLEAN DEFAULT TRUE + }, + """ componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('cookie', syncCookie()), + namedtype.OptionalNamedType('cookie', SyncCookie()), namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True)) ) -class refreshPresent(univ.Sequence): + +class RefreshPresent(univ.Sequence): + """ + refreshPresent [2] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDone BOOLEAN DEFAULT TRUE + }, + """ componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('cookie', syncCookie()), + namedtype.OptionalNamedType('cookie', SyncCookie()), namedtype.DefaultedNamedType('refreshDone', univ.Boolean(True)) ) -class syncUUIDs(univ.SetOf): - componentType = syncUUID() -class syncIdSet(univ.Sequence): +class SyncUUIDs(univ.SetOf): + """ + syncUUIDs SET OF syncUUID + """ + componentType = SyncUUID() + + +class SyncIdSet(univ.Sequence): + """ + syncIdSet [3] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDeletes BOOLEAN DEFAULT FALSE, + syncUUIDs SET OF syncUUID + } + """ componentType = namedtype.NamedTypes( - namedtype.OptionalNamedType('cookie', syncCookie()), + namedtype.OptionalNamedType('cookie', SyncCookie()), namedtype.DefaultedNamedType('refreshDeletes', univ.Boolean(False)), - namedtype.NamedType('syncUUIDs', syncUUIDs()) + namedtype.NamedType('syncUUIDs', SyncUUIDs()) ) -class syncInfoValue(univ.Choice): + +class SyncInfoValue(univ.Choice): + """ + syncInfoValue ::= CHOICE { + newcookie [0] syncCookie, + refreshDelete [1] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDone BOOLEAN DEFAULT TRUE + }, + refreshPresent [2] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDone BOOLEAN DEFAULT TRUE + }, + syncIdSet [3] SEQUENCE { + cookie syncCookie OPTIONAL, + refreshDeletes BOOLEAN DEFAULT FALSE, + syncUUIDs SET OF syncUUID + } + } + """ componentType = namedtype.NamedTypes( namedtype.NamedType( 'newcookie', - syncCookie().subtype( + SyncCookie().subtype( implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0) ) ), namedtype.NamedType( 'refreshDelete', - refreshDelete().subtype( + RefreshDelete().subtype( implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1) ) ), namedtype.NamedType( 'refreshPresent', - refreshPresent().subtype( + RefreshPresent().subtype( implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2) ) ), namedtype.NamedType( 'syncIdSet', - syncIdSet().subtype( + SyncIdSet().subtype( implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3) ) ) ) + class SyncInfoMessage: + """ + The Sync Info Message is an LDAP Intermediate Response Message + [RFC4511] where responseName is the object identifier + 1.3.6.1.4.1.4203.1.9.1.4 and responseValue contains a BER-encoded + syncInfoValue. The criticality is FALSE (and hence absent). + """ responseName = '1.3.6.1.4.1.4203.1.9.1.4' def __init__(self, encodedMessage): - d = decoder.decode(encodedMessage, asn1Spec = syncInfoValue()) + d = decoder.decode(encodedMessage, asn1Spec=SyncInfoValue()) self.newcookie = None self.refreshDelete = None self.refreshPresent = None self.syncIdSet = None - for attr in [ 'newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']: + for attr in ['newcookie', 'refreshDelete', 'refreshPresent', 'syncIdSet']: comp = d[0].getComponentByName(attr) - if comp.hasValue(): + if comp is not None and comp.hasValue(): if attr == 'newcookie': self.newcookie = str(comp) @@ -292,7 +340,7 @@ def __init__(self, encodedMessage): val['syncUUIDs'] = uuids val['refreshDeletes'] = bool(comp.getComponentByName('refreshDeletes')) - setattr(self,attr,val) + setattr(self, attr, val) return @@ -352,9 +400,12 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): """ while True: type, msg, mid, ctrls, n, v = self.result4( - msgid=msgid, timeout=timeout, - add_intermediates=1, add_ctrls=1, all = 0 - ) + msgid=msgid, + timeout=timeout, + add_intermediates=1, + add_ctrls=1, + all=0, + ) if type == 101: # search result. This marks the end of a refreshOnly session. @@ -363,7 +414,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): for c in ctrls: if c.__class__.__name__ != 'SyncDoneControl': continue - self.syncrepl_present(None,refreshDeletes=c.refreshDeletes) + self.syncrepl_present(None, refreshDeletes=c.refreshDeletes) if c.cookie is not None: self.syncrepl_set_cookie(c.cookie) @@ -418,7 +469,6 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): self.syncrepl_present(sim.syncIdSet['syncUUIDs']) if 'cookie' in sim.syncIdSet: self.syncrepl_set_cookie(sim.syncIdSet['cookie']) - pass if all == 0: return True @@ -455,7 +505,6 @@ def syncrepl_present(self, uuids, refreshDeletes=False): If called with uuids set to None and refreshDeletes set to True, syncrepl_present() should reset the list of recorded uuids, without deleting any entries. - """ pass @@ -464,7 +513,6 @@ def syncrepl_delete(self, uuids): Called by syncrepl_poll() to delete entries. A list of UUIDs of the entries to be deleted is given in the uuids parameter. - """ pass @@ -475,7 +523,6 @@ def syncrepl_entry(self, dn, attrs, uuid): The provided uuid is used to identify the provided entry in any future modification (including dn modification), deletion, and presentation operations. - """ pass From 2e885351fb96cdbe193bfeb3d40557d68b7608d2 Mon Sep 17 00:00:00 2001 From: stroeder Date: Sun, 19 Nov 2017 20:24:23 +0000 Subject: [PATCH 083/121] some cosmetics in syncrepl demo script, use logging --- Demo/pyasn1/syncrepl.py | 110 +++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 52 deletions(-) diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 1177d18d..1454eeff 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -7,16 +7,14 @@ Notes: The bound user needs read access to the attributes entryDN and entryCSN. - -This needs the following software: -Python -pyasn1 0.1.4+ -pyasn1-modules -python-ldap 2.4.10+ """ # Import modules from Python standard lib -import shelve,signal,time,sys,logging +import logging +import shelve +import signal +import sys +import time # Import the python-ldap modules import ldap @@ -25,28 +23,34 @@ from ldap.ldapobject import ReconnectLDAPObject from ldap.syncrepl import SyncreplConsumer +logger = logging.getLogger('syncrepl') +logger.setLevel(logging.DEBUG) +logger.addHandler(logging.StreamHandler()) # Global state watcher_running = True ldap_connection = False -class SyncReplConsumer(ReconnectLDAPObject, SyncreplConsumer): +class SyncReplClient(ReconnectLDAPObject, SyncreplConsumer): """ - Syncrepl Consumer interface + Syncrepl Consumer Client """ def __init__(self, db_path, *args, **kwargs): # Initialise the LDAP Connection first ldap.ldapobject.ReconnectLDAPObject.__init__(self, *args, **kwargs) # Now prepare the data store - self.__data = shelve.open(db_path, 'c') + if db_path: + self.__data = shelve.open(db_path, 'c') + else: + self.__data = dict() # We need this for later internal use self.__presentUUIDs = dict() def close_db(self): - # Close the data store properly to avoid corruption - self.__data.close() + # Close the data store properly to avoid corruption + self.__data.close() def syncrepl_get_cookie(self): if 'cookie' in self.__data: @@ -55,7 +59,8 @@ def syncrepl_get_cookie(self): def syncrepl_set_cookie(self,cookie): self.__data['cookie'] = cookie - def syncrepl_entry(self,dn,attributes,uuid): + def syncrepl_entry(self, dn, attributes, uuid): + logger.debug('dn=%r attributes=%r uuid=%r', dn, attributes, uuid) # First we determine the type of change we have here # (and store away the previous data for later if needed) previous_attributes = dict() @@ -69,18 +74,18 @@ def syncrepl_entry(self,dn,attributes,uuid): attributes['dn'] = dn self.__data[uuid] = attributes # Debugging - print 'Detected', change_type, 'of entry:', dn + logger.debug('Detected %s of entry %r', change_type, dn) # If we have a cookie then this is not our first time being run, # so it must be a change if 'ldap_cookie' in self.__data: - self.perform_application_sync(dn, attributes, previous_attributes) + self.perform_application_sync(dn, attributes, previous_attributes) def syncrepl_delete(self,uuids): # Make sure we know about the UUID being deleted, just in case... uuids = [uuid for uuid in uuids if uuid in self.__data] # Delete all the UUID values we know of for uuid in uuids: - print 'Detected deletion of entry:', self.__data[uuid]['dn'] + logger.debug('Detected deletion of entry %r', self.__data[uuid]['dn']) del self.__data[uuid] def syncrepl_present(self,uuids,refreshDeletes=False): @@ -105,10 +110,10 @@ def syncrepl_present(self,uuids,refreshDeletes=False): self.__presentUUIDs[uuid] = True def syncrepl_refreshdone(self): - print 'Initial synchronization is now done, persist phase begins' + logger.info('Initial synchronization is now done, persist phase begins') def perform_application_sync(self,dn,attributes,previous_attributes): - print 'Performing application sync for:', dn + logger.info('Performing application sync for %r', dn) return True @@ -116,67 +121,69 @@ def perform_application_sync(self,dn,attributes,previous_attributes): def commenceShutdown(signum, stack): # Declare the needed global variables global watcher_running, ldap_connection - print 'Shutting down!' + logger.warn('Shutting down!') # We are no longer running watcher_running = False # Tear down the server connection - if( ldap_connection ): - ldap_connection.close_db() - ldap_connection.unbind_s() - del ldap_connection + if ldap_connection: + ldap_connection.close_db() + ldap_connection.unbind_s() + del ldap_connection # Shutdown sys.exit(0) # Time to actually begin execution # Install our signal handlers -signal.signal(signal.SIGTERM,commenceShutdown) -signal.signal(signal.SIGINT,commenceShutdown) +signal.signal(signal.SIGTERM, commenceShutdown) +signal.signal(signal.SIGINT, commenceShutdown) try: - ldap_url = ldapurl.LDAPUrl(sys.argv[1]) - database_path = sys.argv[2] + ldap_url = ldapurl.LDAPUrl(sys.argv[1]) + database_path = sys.argv[2] except IndexError,e: - print 'Usage:' - print sys.argv[0], ' ' - print sys.argv[0], '\'ldap://127.0.0.1/cn=users,dc=test'\ - '?*'\ - '?sub'\ - '?(objectClass=*)'\ - '?bindname=uid=admin%2ccn=users%2cdc=test,'\ - 'X-BINDPW=password\' db.shelve' + print ( + 'Usage:\n' + '{script_name} \n' + '{script_name} "ldap://127.0.0.1/cn=users,dc=test' + '?*' + '?sub' + '?(objectClass=*)' + '?bindname=uid=admin%2ccn=users%2cdc=test,' + 'X-BINDPW=password" db.shelve' + ).format(script_name=sys.argv[0]) sys.exit(1) except ValueError,e: - print 'Error parsing command-line arguments:',str(e) - sys.exit(1) + print 'Error parsing command-line arguments:', str(e) + sys.exit(1) while watcher_running: - print 'Connecting to LDAP server now...' + logger.info('Connecting to %s now...', ldap_url.initializeUrl()) # Prepare the LDAP server connection (triggers the connection as well) - ldap_connection = SyncReplConsumer(database_path, ldap_url.initializeUrl()) + ldap_connection = SyncReplClient(database_path, ldap_url.initializeUrl()) # Now we login to the LDAP server try: ldap_connection.simple_bind_s(ldap_url.who, ldap_url.cred) - except ldap.INVALID_CREDENTIALS, e: - print 'Login to LDAP server failed: ', str(e) + except ldap.INVALID_CREDENTIALS, err: + logger.error('Login to LDAP server failed: %s', err) sys.exit(1) except ldap.SERVER_DOWN: - print 'LDAP server is down, going to retry.' + logger.warn('LDAP server is down, going to retry.') time.sleep(5) continue # Commence the syncing - print 'Commencing sync process' + logger.debug('Commencing sync process') ldap_search = ldap_connection.syncrepl_search( - ldap_url.dn or '', - ldap_url.scope or ldap.SCOPE_SUBTREE, - mode = 'refreshAndPersist', - attrlist=ldap_url.attrs, - filterstr = ldap_url.filterstr or '(objectClass=*)' + ldap_url.dn or '', + ldap_url.scope or ldap.SCOPE_SUBTREE, + mode = 'refreshAndPersist', + attrlist=ldap_url.attrs, + filterstr = ldap_url.filterstr or '(objectClass=*)' ) try: @@ -185,10 +192,9 @@ def commenceShutdown(signum, stack): except KeyboardInterrupt: # User asked to exit commenceShutdown(None, None) - pass - except Exception, e: + except Exception, err: # Handle any exception if watcher_running: - print 'Encountered a problem, going to retry. Error:', str(e) + logger.exception('Unhandled exception, going to retry: %s', err) + logger.info('Going to retry after 5 secs') time.sleep(5) - pass From 1c26ef117568b336e9de613b7ebc06f5be69b4ba Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 10:53:51 +0000 Subject: [PATCH 084/121] raise ldap.PROTOCOL_ERROR if compare operation returned a wrong result --- Lib/ldap/ldapobject.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 855dabff..805b6e34 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -286,12 +286,14 @@ def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None): def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls) try: - resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout) + ldap_res = self.result3(msgid,all=1,timeout=self.timeout) except ldap.COMPARE_TRUE: return 1 except ldap.COMPARE_FALSE: return 0 - return None + raise ldap.PROTOCOL_ERROR( + 'Compare operation returned wrong result: %r' % (ldap_res) + ) def compare(self,dn,attr,value): return self.compare_ext(dn,attr,value,None,None) From d40f13c88f1b678f1750204c6bb1ef06d6737bdb Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 11:42:19 +0000 Subject: [PATCH 085/121] added tests for ldap.syncrepl --- CHANGES | 1 + Tests/t_ldap_syncrepl.py | 469 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 470 insertions(+) create mode 100644 Tests/t_ldap_syncrepl.py diff --git a/CHANGES b/CHANGES index 1d3bba02..6bd4f73f 100644 --- a/CHANGES +++ b/CHANGES @@ -26,6 +26,7 @@ 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 diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py new file mode 100644 index 00000000..72cbce3a --- /dev/null +++ b/Tests/t_ldap_syncrepl.py @@ -0,0 +1,469 @@ +# -*- coding: utf-8 -*- +""" +Automatic tests for python-ldap's module ldap.syncrepl + +See http://www.python-ldap.org/ for details. + +$Id: t_ldap_syncrepl.py,v 1.1 2017/11/20 11:42:19 stroeder Exp $ +""" + + +import os +import unittest +import shelve + +from slapdtest import SlapdObject, SlapdTestCase + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +import ldap +from ldap.ldapobject import SimpleLDAPObject +from ldap.syncrepl import SyncreplConsumer + +# a template string for generating simple slapd.conf file +SLAPD_CONF_PROVIDER_TEMPLATE = r""" +serverID %(serverid)s +moduleload back_%(database)s +moduleload syncprov +include "%(schema_prefix)s/core.schema" +loglevel %(loglevel)s +allow bind_v2 + +authz-regexp + "gidnumber=%(root_gid)s\\+uidnumber=%(root_uid)s,cn=peercred,cn=external,cn=auth" + "%(rootdn)s" + +database %(database)s +directory "%(directory)s" +suffix "%(suffix)s" +rootdn "%(rootdn)s" +rootpw "%(rootpw)s" +overlay syncprov +syncprov-checkpoint 100 10 +syncprov-sessionlog 100 +index objectclass,entryCSN,entryUUID eq +""" + +# Define initial data load, both as an LDIF and as a dictionary. +LDIF_TEMPLATE = """dn: %(suffix)s +objectClass: dcObject +objectClass: organization +dc: %(dc)s +o: %(dc)s + +dn: %(rootdn)s +objectClass: applicationProcess +objectClass: simpleSecurityObject +cn: %(rootcn)s +userPassword: %(rootpw)s + +dn: cn=Foo1,%(suffix)s +objectClass: organizationalRole +cn: Foo1 + +dn: cn=Foo2,%(suffix)s +objectClass: organizationalRole +cn: Foo2 + +dn: cn=Foo3,%(suffix)s +objectClass: organizationalRole +cn: Foo3 + +dn: ou=Container,%(suffix)s +objectClass: organizationalUnit +ou: Container + +dn: cn=Foo4,ou=Container,%(suffix)s +objectClass: organizationalRole +cn: Foo4 + +""" + +# NOTE: For the dict, it needs to be kept up-to-date as we make changes! +LDAP_ENTRIES = { + 'ou=Container,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['organizationalUnit'], + 'ou': ['Container'] + }, + 'cn=Foo2,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['organizationalRole'], + 'cn': ['Foo2'] + }, + 'cn=Foo4,ou=Container,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['organizationalRole'], + 'cn': ['Foo4'] + }, + 'cn=Manager,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['applicationProcess', 'simpleSecurityObject'], + 'userPassword': ['password'], + 'cn': ['Manager'] + }, + 'cn=Foo3,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['organizationalRole'], + 'cn': ['Foo3'] + }, + 'cn=Foo1,dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['organizationalRole'], + 'cn': ['Foo1'] + }, + 'dc=slapd-test,dc=python-ldap,dc=org': { + 'objectClass': ['dcObject', 'organization'], + 'dc': ['slapd-test'], + 'o': ['slapd-test'] + } +} + + +class SyncreplProvider(SlapdObject): + slapd_conf_template = SLAPD_CONF_PROVIDER_TEMPLATE + + +class SyncreplClient(SimpleLDAPObject, SyncreplConsumer): + """ + This is a very simple class to start up the syncrepl search + and handle callbacks that come in. + + Needs to be separate, because once an LDAP client starts a syncrepl + search, it can't be used for anything else. + """ + server_class = SyncreplProvider + + def __init__(self, uri, dn, password, storage=None): + """ + Set up our object by creating a search client, connecting, and binding. + """ + + if storage is not None: + self.data = shelve.open(storage) + self.uuid_dn = shelve.open(storage + 'uuid_dn') + self.dn_attrs = shelve.open(storage + 'dn_attrs') + self.using_shelve = True + else: + self.data = {} + self.uuid_dn = {} + self.dn_attrs = {} + self.using_shelve = False + + self.data['cookie'] = None + self.present = [] + self.refresh_done = False + + SimpleLDAPObject.__init__(self, uri) + self.simple_bind_s(dn, password) + + + def unbind_s(self): + """ + In addition to unbinding from LDAP, we need to close the shelf. + """ + if self.using_shelve is True: + self.data.close() + self.uuid_dn.close() + self.dn_attrs.close() + SimpleLDAPObject.unbind_s(self) + + + def search(self, search_base, search_mode): + """ + Start a syncrepl search operation, given a base DN and search mode. + """ + self.search_id = self.syncrepl_search( + search_base, + ldap.SCOPE_SUBTREE, + mode=search_mode, + filterstr='(objectClass=*)' + ) + + + def cancel(self): + """ + A simple wrapper to call parent class with syncrepl search ID. + """ + SimpleLDAPObject.cancel(self, self.search_id) + + + def poll(self, timeout=None, all=0): + """ + Take the params, add the syncrepl search ID, and call the proper poll. + """ + return self.syncrepl_poll( + self.search_id, + timeout=timeout, + all=all + ) + + + def syncrepl_get_cookie(self): + """ + Pull cookie from storage, if one exists. + """ + return self.data['cookie'] + + + def syncrepl_set_cookie(self, cookie): + """ + Update stored cookie. + """ + self.data['cookie'] = cookie + + + def syncrepl_refreshdone(self): + """ + Just update a variable. + """ + self.refresh_done = True + + + def syncrepl_delete(self, uuids): + """ + Delete the given items from both maps. + """ + for uuid in uuids: + del self.dn_attrs[self.uuid_dn[uuid]] + del self.uuid_dn[uuid] + + + def syncrepl_entry(self, dn, attrs, uuid): + """ + Handles adds and changes (including DN changes). + """ + if uuid in self.uuid_dn: + # Catch changing DNs. + if dn != self.uuid_dn[uuid]: + # Delete data associated with old DN. + del self.dn_attrs[self.uuid_dn[uuid]] + + # Update both maps. + self.uuid_dn[uuid] = dn + self.dn_attrs[dn] = attrs + + + def syncrepl_present(self, uuids, refreshDeletes=False): + """ + The 'present' message from the LDAP server is the most complicated + part of the refresh phase. Suggest looking here for more info: + http://syncrepl-client.readthedocs.io/en/latest/client.html + """ + if (uuids is not None) and (refreshDeletes is False): + self.present.extend(uuids) + + elif (uuids is None) and (refreshDeletes is False): + deleted_uuids = list() + for uuid in self.uuid_dn.keys(): + if uuid not in self.present: + deleted_uuids.append(uuid) + + if len(deleted_uuids) > 0: + self.syncrepl_delete(deleted_uuids) + + elif (uuids is not None) and (refreshDeletes is True): + self.syncrepl_delete(uuids) + + elif (uuids is None) and (refreshDeletes is True): + pass + + +class Test00_Syncrepl(SlapdTestCase): + """ + This is a test of all the basic Syncrepl operations. It covers starting a + search (both types of search), doing the refresh part of the search, + and checking that we got everything that we expected. We also test that + timeouts and cancellation are working properly. + """ + + server_class = SyncreplProvider + ldap_object_class = SimpleLDAPObject + + @classmethod + def setUpClass(cls): + super(Test00_Syncrepl, cls).setUpClass() + # insert some Foo* objects via ldapadd + cls.server.ldapadd( + LDIF_TEMPLATE % { + 'suffix':cls.server.suffix, + 'rootdn':cls.server.root_dn, + 'rootcn':cls.server.root_cn, + 'rootpw':cls.server.root_pw, + 'dc': cls.server.suffix.split(',')[0][3:], + } + ) + + + def setUp(self): + try: + self._ldap_conn + except AttributeError: + # open local LDAP connection + self._ldap_conn = self._open_ldap_conn() + + + def tearDown(self): + self.tester.unbind_s() + + + def test_refreshOnly_search(self): + ''' + Test to see if we can initialize a syncrepl search. + ''' + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshOnly' + ) + + + def test_refreshAndPersist_search(self): + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + + def test_refreshOnly_poll_full(self): + """ + Test doing a full refresh cycle, and check what we got. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshOnly' + ) + poll_result = self.tester.poll( + all=1, + timeout=None + ) + self.assertFalse(poll_result) + self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + + + def test_refreshAndPersist_poll_only(self): + """ + Test the refresh part of refresh-and-persist, and check what we got. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Make sure to stop the test before going into persist mode. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + + + def test_refreshAndPersist_timeout(self): + """ + Make sure refreshAndPersist can handle a search with timeouts. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Run a quick refresh, that shouldn't have any changes. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + # Again, server data should not have changed. + self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + + # Run a search with timeout. + # Nothing is changing the server, so it shoud timeout. + self.assertRaises( + ldap.TIMEOUT, + self.tester.poll, + all=0, + timeout=1 + ) + + + def test_refreshAndPersist_cancelled(self): + """ + Make sure refreshAndPersist can handle cancelling a syncrepl search. + """ + self.tester = SyncreplClient( + self.server.ldap_uri, + self.server.root_dn, + self.server.root_pw + ) + self.tester.search( + self.server.suffix, + 'refreshAndPersist' + ) + + # Run a quick refresh, that shouldn't have any changes. + while self.tester.refresh_done is not True: + poll_result = self.tester.poll( + all=0, + timeout=None + ) + self.assertTrue(poll_result) + + # Again, server data should not have changed. + self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + + # Request cancellation. + self.tester.cancel() + + # Run another poll, without timeout, but which should cancel out. + self.assertRaises( + ldap.CANCELLED, + self.tester.poll, + all=1, + timeout=None + ) + + # Server data should still be intact. + self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + + + # TODO: + # * Make a new client, with a data store, and close. Then, load a new + # client with the same datastore, and see if the data store loads OK. + # * Make a new client, with a data store, and close. Then, load a new + # client with the same datastore. Delete an entry, and the cookie. + # Start the sync, and everything should sync up OK. + # * Load the refreshOnly client, using existing data. Make a change + # on the server, and the client should pick it up in the refresh phase. + # * Load the refreshAndPersist client, using existing data. Make a change + # on the server, and the client should pick it up in the refresh phase. + # * Load the refreshAndPersist client, using existing data. Let the + # refresh phase complete. Make a change on the server, and the client + # should pick it up during the persist phase. + + +if __name__ == '__main__': + unittest.main() From 773d7fbe9e045043b3d1de80af2f63d670d18ef3 Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 16:04:56 +0000 Subject: [PATCH 086/121] another LDAPObject test --- Tests/t_ldapobject.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index dfe2412f..196a9f3e 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -188,6 +188,13 @@ def test006_sasl_extenal_bind_s(self): l = self.ldap_object_class(self.server.ldapi_uri) l.sasl_external_bind_s(authz_id=authz_id) self.assertEqual(l.whoami_s(), authz_id.lower()) + + def test007_timeout(self): + l = self.ldap_object_class(self.server.ldap_uri) + m = l.search_ext(self.server.suffix, ldap.SCOPE_SUBTREE, '(objectClass=*)') + l.abandon(m) + with self.assertRaises(ldap.TIMEOUT): + result = l.result(m, timeout=0.001) class Test01_ReconnectLDAPObject(Test00_SimpleLDAPObject): From d448fb999c8f3a2610988a0bbada42fcecc53c36 Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 17:33:06 +0000 Subject: [PATCH 087/121] assume C extension API for Python 2.7+ --- CHANGES | 1 + Modules/LDAPObject.c | 21 ++++++++++++++++++--- Modules/LDAPObject.h | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6bd4f73f..6f964819 100644 --- a/CHANGES +++ b/CHANGES @@ -9,6 +9,7 @@ Modules/ * 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 diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index 3291379e..95387a06 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1402,11 +1402,10 @@ setattr(LDAPObject* self, char* name, PyObject* value) PyTypeObject LDAP_Type = { #if defined(MS_WINDOWS) || defined(__CYGWIN__) /* see http://www.python.org/doc/FAQ.html#3.24 */ - PyObject_HEAD_INIT(NULL) + PyVarObject_HEAD_INIT(NULL, 0) #else /* ! MS_WINDOWS */ - PyObject_HEAD_INIT(&PyType_Type) + PyVarObject_HEAD_INIT(&PyType_Type, 0) #endif /* MS_WINDOWS */ - 0, /*ob_size*/ "LDAP", /*tp_name*/ sizeof(LDAPObject), /*tp_basicsize*/ 0, /*tp_itemsize*/ @@ -1421,4 +1420,20 @@ PyTypeObject LDAP_Type = { 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash*/ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + 0, /*tp_flags*/ + 0, /*tp_doc*/ + 0, /*tp_traverse*/ + 0, /*tp_clear*/ + 0, /*tp_richcompare*/ + 0, /*tp_weaklistoffset*/ + 0, /*tp_iter*/ + 0, /*tp_iternext*/ + methods, /*tp_methods*/ + 0, /*tp_members*/ + 0, /*tp_getset*/ }; diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h index a0adc3f0..8cd6fc3e 100644 --- a/Modules/LDAPObject.h +++ b/Modules/LDAPObject.h @@ -25,7 +25,7 @@ typedef struct { } LDAPObject; extern PyTypeObject LDAP_Type; -#define LDAPObject_Check(v) ((v)->ob_type == &LDAP_Type) +#define LDAPObject_Check(v) (Py_TYPE(v) == &LDAP_Type) extern LDAPObject *newLDAPObject( LDAP* ); From 608cda54bf03dfd146a787580f36c550e9d1d4ac Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 17:34:15 +0000 Subject: [PATCH 088/121] prepare release 2.5.2 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6f964819..b3ef0d58 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,5 @@ ---------------------------------------------------------------- -Released 2.5.2 2017-11-xx +Released 2.5.2 2017-11-20 Changes since 2.5.1: From a603396e140418f774748bf134e827a10204c61b Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 17:39:12 +0000 Subject: [PATCH 089/121] use single quotes --- Tests/t_cext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index e1d79d36..cac661ec 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -192,7 +192,7 @@ def test_anon_rootdse_search(self): l = self._open_conn(bind=False) # see if we can get the rootdse with anon search (without prior bind) m = l.search_ext( - "", + '', _ldap.SCOPE_BASE, '(objectClass=*)', ['objectClass', 'namingContexts'], From 798cb611d3223b31fa2b5852ca280d0aac6bd116 Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 19:34:39 +0000 Subject: [PATCH 090/121] use ..assertEqual() instead of .assertEquals() in t_ldap_syncrepl.py --- Tests/t_ldap_syncrepl.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 72cbce3a..b9640fc4 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -4,7 +4,7 @@ See http://www.python-ldap.org/ for details. -$Id: t_ldap_syncrepl.py,v 1.1 2017/11/20 11:42:19 stroeder Exp $ +$Id: t_ldap_syncrepl.py,v 1.2 2017/11/20 19:34:39 stroeder Exp $ """ @@ -347,7 +347,7 @@ def test_refreshOnly_poll_full(self): timeout=None ) self.assertFalse(poll_result) - self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) def test_refreshAndPersist_poll_only(self): @@ -372,7 +372,7 @@ def test_refreshAndPersist_poll_only(self): ) self.assertTrue(poll_result) - self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) def test_refreshAndPersist_timeout(self): @@ -398,7 +398,7 @@ def test_refreshAndPersist_timeout(self): self.assertTrue(poll_result) # Again, server data should not have changed. - self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) # Run a search with timeout. # Nothing is changing the server, so it shoud timeout. @@ -433,7 +433,7 @@ def test_refreshAndPersist_cancelled(self): self.assertTrue(poll_result) # Again, server data should not have changed. - self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) # Request cancellation. self.tester.cancel() @@ -447,7 +447,7 @@ def test_refreshAndPersist_cancelled(self): ) # Server data should still be intact. - self.assertEquals(self.tester.dn_attrs, LDAP_ENTRIES) + self.assertEqual(self.tester.dn_attrs, LDAP_ENTRIES) # TODO: From 3141ce062a45638f1329e67334fe1aa7fd8ddbf7 Mon Sep 17 00:00:00 2001 From: stroeder Date: Mon, 20 Nov 2017 19:35:38 +0000 Subject: [PATCH 091/121] removed CVS-Id in t_ldap_syncrepl.py --- Tests/t_ldap_syncrepl.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index b9640fc4..8f39c675 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -3,8 +3,6 @@ Automatic tests for python-ldap's module ldap.syncrepl See http://www.python-ldap.org/ for details. - -$Id: t_ldap_syncrepl.py,v 1.2 2017/11/20 19:34:39 stroeder Exp $ """ From aeeac962b7e122e3ab0465658e9148beea00d60f Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 16:16:47 +0100 Subject: [PATCH 092/121] Infra: Condense CHANGELOG from 2.5 --- CHANGES | 55 +++++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/CHANGES b/CHANGES index b3ef0d58..92b4737d 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 093/121] 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 00000000..072e2034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ + +# Auto-generated +.*.swp +*.pyc +__pycache__/ +.tox + +# shared libs installed by 'setup.py test' +/Lib/*.so* +/Lib/*.dylib +/Lib/*.pyd + +# Build related +*.egg-info +build/ +dist/ +PKG-INFO diff --git a/CHANGES b/CHANGES index 92b4737d..a8ad44a8 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 094/121] 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 58df3b36..04506b36 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 996e6dac..b293aa20 100644 --- a/Demo/Lib/ldapurl/urlsearch.py +++ b/Demo/Lib/ldapurl/urlsearch.py @@ -3,30 +3,30 @@ No output of LDAP data is produced except trace output. """ - +from __future__ import print_function import sys,getpass,ldap,ldapurl try: ldapUrl = ldapurl.LDAPUrl(ldapUrl=sys.argv[1]) except IndexError: - print 'Usage: %s [LDAP URL]' % (sys.argv[0]) + print('Usage: %s [LDAP URL]' % (sys.argv[0])) sys.exit(1) for a in [ 'urlscheme','hostport','dn','attrs','scope', 'filterstr','extensions','who','cred' ]: - print a,repr(getattr(ldapUrl,a)) + print(a,repr(getattr(ldapUrl,a))) l = ldap.initialize(ldapUrl.initializeUrl(),trace_level=1) if ldapUrl.who!=None: if ldapUrl.cred!=None: cred=ldapUrl.cred else: - print 'Enter password for simple bind with',repr(ldapUrl.who) + print('Enter password for simple bind with',repr(ldapUrl.who)) cred=getpass.getpass() l.simple_bind_s(ldapUrl.who,cred) res = l.search_s(ldapUrl.dn,ldapUrl.scope,ldapUrl.filterstr,ldapUrl.attrs) -print len(res),'search results' +print(len(res),'search results') diff --git a/Demo/initialize.py b/Demo/initialize.py index ab2647e7..952b3f4b 100644 --- a/Demo/initialize.py +++ b/Demo/initialize.py @@ -7,6 +7,7 @@ ldaps://localhost:1391 (LDAP over SSL) ldapi://%2ftmp%2fopenldap2 (domain socket /tmp/openldap2) """ +from __future__ import print_function import sys,os,ldap @@ -23,10 +24,10 @@ # Complete path name of the file containing all trusted CA certs CACERTFILE='/etc/ssl/ca-bundle.pem' -print """################################################################## +print("""################################################################## # LDAPv3 connection with StartTLS ext. op. ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldap://localhost:1390',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) @@ -44,8 +45,8 @@ # Now try StartTLS extended operation l.start_tls_s() -print '***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION) -print '***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER) +print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION)) +print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER)) # Try an explicit anon bind to provoke failure l.simple_bind_s('','') @@ -53,10 +54,10 @@ # Close connection l.unbind_s() -print """################################################################## +print("""################################################################## # LDAPv3 connection over SSL ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldaps://localhost:1391',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) @@ -74,16 +75,16 @@ # Try an explicit anon bind to provoke failure l.simple_bind_s('','') -print '***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION) -print '***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER) +print('***ldap.OPT_X_TLS_VERSION',l.get_option(ldap.OPT_X_TLS_VERSION)) +print('***ldap.OPT_X_TLS_CIPHER',l.get_option(ldap.OPT_X_TLS_CIPHER)) # Close connection l.unbind_s() -print """################################################################## +print("""################################################################## # LDAPv3 connection over Unix domain socket ################################################################## -""" +""") # Create LDAPObject instance l = ldap.initialize('ldapi://%2ftmp%2fopenldap-socket',trace_level=ldapmodule_trace_level,trace_file=ldapmodule_trace_file) diff --git a/Demo/ldapcontrols.py b/Demo/ldapcontrols.py index 214042d3..a5ba8d34 100644 --- a/Demo/ldapcontrols.py +++ b/Demo/ldapcontrols.py @@ -1,15 +1,16 @@ +from __future__ import print_function import ldap,ldapurl,pprint from ldap.controls import LDAPControl,BooleanControl l = ldap.initialize('ldap://localhost:1390',trace_level=2) -print 60*'#' +print(60*'#') pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS)) l.manage_dsa_it(1,1) pprint.pprint(l.get_option(ldap.OPT_SERVER_CONTROLS)) -print 60*'#' +print(60*'#') # Search with ManageDsaIT control (which has no value) pprint.pprint(l.search_ext_s( @@ -19,7 +20,7 @@ ['*','+'], serverctrls = [ LDAPControl('2.16.840.1.113730.3.4.2',1,None) ], )) -print 60*'#' +print(60*'#') # Search with Subentries control (which has boolean value) pprint.pprint(l.search_ext_s( @@ -30,4 +31,4 @@ serverctrls = [ BooleanControl('1.3.6.1.4.1.4203.1.10.1',1,1) ], )) -print 60*'#' +print(60*'#') diff --git a/Demo/ldapurl_search.py b/Demo/ldapurl_search.py index 2c630090..07ffbca5 100644 --- a/Demo/ldapurl_search.py +++ b/Demo/ldapurl_search.py @@ -1,3 +1,5 @@ +from __future__ import print_function + import sys,pprint,ldap from ldap.ldapobject import LDAPObject @@ -15,7 +17,7 @@ class MyLDAPUrl(LDAPUrl): ldap_url = MyLDAPUrl(sys.argv[1]) trace_level = int(ldap_url.trace_level or '0') -print '***trace_level',trace_level +print('***trace_level',trace_level) ldap.trace_level = trace_level @@ -37,6 +39,6 @@ class MyLDAPUrl(LDAPUrl): pprint.pprint(result) -print '***DIAGNOSTIC_MESSAGE',repr(l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE)) +print('***DIAGNOSTIC_MESSAGE',repr(l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE))) l.unbind_s() diff --git a/Demo/matchedvalues.py b/Demo/matchedvalues.py index 4de3e8a4..59c594ff 100644 --- a/Demo/matchedvalues.py +++ b/Demo/matchedvalues.py @@ -27,16 +27,17 @@ # Matched values control: (mail=*@example.org) # dn: uid=jsmith,ou=People,dc=example,dc=com # mail: jsmith@example.org +from __future__ import print_function import ldap from ldap.controls import MatchedValuesControl def print_result(search_result): for n in range(len(search_result)): - print "dn: %s" % search_result[n][0] + print("dn: %s" % search_result[n][0]) for attr in search_result[n][1].keys(): for i in range(len(search_result[n][1][attr])): - print "%s: %s" % (attr, search_result[n][1][attr][i]) + print("%s: %s" % (attr, search_result[n][1][attr][i])) print @@ -51,13 +52,13 @@ def print_result(search_result): mv = MatchedValuesControl(criticality=True, controlValue=control_filter) res = ld.search_ext_s(base, scope, filter, attrlist = ['mail']) -print "LDAP filter used: %s" % filter -print "Requesting 'mail' attribute back" +print("LDAP filter used: %s" % filter) +print("Requesting 'mail' attribute back") print -print "No matched values control:" +print("No matched values control:") print_result(res) res = ld.search_ext_s(base, scope, filter, attrlist = ['mail'], serverctrls = [mv]) -print "Matched values control: %s" % control_filter +print("Matched values control: %s" % control_filter) print_result(res) diff --git a/Demo/options.py b/Demo/options.py index fb372093..8b4e2159 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -1,27 +1,28 @@ +from __future__ import print_function import ldap host="localhost:1390" -print "API info:",ldap.get_option(ldap.OPT_API_INFO) -print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL) -#print "Setting debug level to 255..." +print("API info:",ldap.get_option(ldap.OPT_API_INFO)) +print("debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)) +#print("Setting debug level to 255...") #ldap.set_option(ldap.OPT_DEBUG_LEVEL,255) -#print "debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL) -print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT) -print "Setting default size limit to 10..." +#print("debug level:",ldap.get_option(ldap.OPT_DEBUG_LEVEL)) +print("default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)) +print("Setting default size limit to 10...") ldap.set_option(ldap.OPT_SIZELIMIT,10) -print "default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT) -print "Creating connection to",host,"..." +print("default size limit:",ldap.get_option(ldap.OPT_SIZELIMIT)) +print("Creating connection to",host,"...") l=ldap.init(host) -print "size limit:",l.get_option(ldap.OPT_SIZELIMIT) -print "Setting connection size limit to 20..." +print("size limit:",l.get_option(ldap.OPT_SIZELIMIT)) +print("Setting connection size limit to 20...") l.set_option(ldap.OPT_SIZELIMIT,20) -print "size limit:",l.get_option(ldap.OPT_SIZELIMIT) -#print "Setting time limit to 60 secs..." +print("size limit:",l.get_option(ldap.OPT_SIZELIMIT)) +#print("Setting time limit to 60 secs...") l.set_option(ldap.OPT_TIMELIMIT,60) -#print "time limit:",l.get_option(ldap.OPT_TIMELIMIT) -print "Binding..." +#print("time limit:",l.get_option(ldap.OPT_TIMELIMIT)) +print("Binding...") l.simple_bind_s("","") diff --git a/Demo/page_control.py b/Demo/page_control.py index 0fc904b4..8238ede3 100644 --- a/Demo/page_control.py +++ b/Demo/page_control.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from __future__ import print_function url = "ldap://localhost:1390" base = "dc=stroeder,dc=de" @@ -41,20 +42,20 @@ pages = 0 while True: pages += 1 - print '-'*60 - print "Getting page %d" % (pages) + print('-'*60) + print("Getting page %d" % (pages)) rtype, rdata, rmsgid, serverctrls = l.result3(msgid,resp_ctrl_classes=known_ldap_resp_ctrls) - print '%d results' % len(rdata) - print 'serverctrls=',pprint.pprint(serverctrls) - print 'rdata=',pprint.pprint(rdata) + print('%d results' % len(rdata)) + print('serverctrls=',pprint.pprint(serverctrls)) + print('rdata=',pprint.pprint(rdata)) pctrls = [ c for c in serverctrls if c.controlType == SimplePagedResultsControl.controlType ] if pctrls: - print 'pctrls[0].size',repr(pctrls[0].size) - print 'pctrls[0].cookie',repr(pctrls[0].cookie) + print('pctrls[0].size',repr(pctrls[0].size)) + print('pctrls[0].cookie',repr(pctrls[0].cookie)) if pctrls[0].cookie: # Copy cookie from response control to request control req_ctrl.cookie = pctrls[0].cookie @@ -68,7 +69,7 @@ else: break else: - print "Warning: Server ignores RFC 2696 control." + print("Warning: Server ignores RFC 2696 control.") break l.unbind_s() diff --git a/Demo/paged_search_ext_s.py b/Demo/paged_search_ext_s.py index d0e0982f..457d79c1 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 1030f0eb..cc5d22cd 100644 --- a/Demo/passwd_ext_op.py +++ b/Demo/passwd_ext_op.py @@ -1,6 +1,7 @@ """ Example showing the use of the password extended operation. """ +from __future__ import print_function import sys,ldap,ldapurl,getpass @@ -11,9 +12,9 @@ lu = ldapurl.LDAPUrl(sys.argv[1]) -print 'Old password' +print('Old password') oldpw = getpass.getpass() -print 'New password' +print('New password') newpw = getpass.getpass() # Set path name of file containing all CA certificates diff --git a/Demo/pyasn1/dds.py b/Demo/pyasn1/dds.py index 656b4397..71722b08 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 10985293..0e7153de 100644 --- a/Demo/pyasn1/derefcontrol.py +++ b/Demo/pyasn1/derefcontrol.py @@ -3,6 +3,7 @@ This sample script demonstrates the use of the dereference control (see https://tools.ietf.org/html/draft-masarati-ldap-deref) """ +from __future__ import print_function import pprint,ldap,ldap.modlist,ldap.resiter @@ -29,8 +30,8 @@ class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor): } ) -print 'pyasn1 output of request control:' -print dc._derefSpecs().prettyPrint() +print('pyasn1 output of request control:') +print(dc._derefSpecs().prettyPrint()) msg_id = l.search_ext( 'dc=demo1,dc=freeipa,dc=org', @@ -43,6 +44,6 @@ class MyLDAPObject(ldap.ldapobject.LDAPObject,ldap.resiter.ResultProcessor): for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id,add_ctrls=1): for dn,entry,deref_control in res_data: # process dn and entry - print dn,entry['objectClass'] + print(dn,entry['objectClass']) if deref_control: pprint.pprint(deref_control[0].derefRes) diff --git a/Demo/pyasn1/noopsearch.py b/Demo/pyasn1/noopsearch.py index 08635dea..88d2d894 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 8722356c..97720b07 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 02852b19..b2300ada 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 b52ea910..10faa2b2 100644 --- a/Demo/pyasn1/readentrycontrol.py +++ b/Demo/pyasn1/readentrycontrol.py @@ -4,6 +4,7 @@ Originally contributed by Andreas Hasenack """ +from __future__ import print_function import pprint,ldap,ldap.modlist @@ -14,10 +15,10 @@ l = ldap.initialize(uri,trace_level=2) l.simple_bind_s('uid=diradm,ou=schulung,dc=stroeder,dc=local','testsecret') -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Add new entry #--------------------------------------------------------------------------- -""" +""") new_test_dn = "uid=ablume,ou=Users,ou=schulung,dc=stroeder,dc=local" new_test_dn2 = "uid=ablume2,ou=Users,ou=schulung,dc=stroeder,dc=local" @@ -38,13 +39,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Modify entry #--------------------------------------------------------------------------- -""" +""") pr = PreReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN']) @@ -54,8 +55,8 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) pr = PostReadControl(criticality=True,attrList=['uidNumber','gidNumber','entryCSN']) @@ -65,13 +66,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Rename entry #--------------------------------------------------------------------------- -""" +""") pr = PostReadControl(criticality=True,attrList=['uid']) msg_id = l.rename( @@ -81,8 +82,8 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) pr = PreReadControl(criticality=True,attrList=['uid']) msg_id = l.rename( @@ -92,13 +93,13 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) -print """#--------------------------------------------------------------------------- +print("""#--------------------------------------------------------------------------- # Delete entry #--------------------------------------------------------------------------- -""" +""") pr = PreReadControl(criticality=True,attrList=['*','+']) msg_id = l.delete_ext( @@ -106,5 +107,5 @@ serverctrls = [pr] ) _,_,_,resp_ctrls = l.result3(msg_id) -print "resp_ctrls[0].dn:",resp_ctrls[0].dn -print "resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry) +print("resp_ctrls[0].dn:",resp_ctrls[0].dn) +print("resp_ctrls[0].entry:";pprint.pprint(resp_ctrls[0].entry)) diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 1a1eab10..337c5e99 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 1454eeff..479a626d 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 1bb6fd70..91d7528f 100644 --- a/Demo/rename.py +++ b/Demo/rename.py @@ -1,10 +1,11 @@ +from __future__ import print_function import ldap from getpass import getpass # Create LDAPObject instance l = ldap.initialize('ldap://localhost:1389',trace_level=1) -print 'Password:' +print('Password:') cred = getpass() try: @@ -15,7 +16,7 @@ # Try a bind to provoke failure if protocol version is not supported l.bind_s('cn=root,dc=stroeder,dc=com',cred,ldap.AUTH_SIMPLE) - print 'Using rename_s():' + print('Using rename_s():') l.rename_s( 'uid=fred,ou=Unstructured testing tree,dc=stroeder,dc=com', diff --git a/Demo/resiter.py b/Demo/resiter.py index ff9fe5a1..d6796baf 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 05af6527..7ff75bf1 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 c8103f3f..9f650d2d 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 3b3a091e..ef8bcec1 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 e4cf4d0f..6004c9ef 100644 --- a/Demo/simple.py +++ b/Demo/simple.py @@ -1,3 +1,4 @@ +from __future__ import print_function import sys,getpass import ldap @@ -14,7 +15,7 @@ try: dn = "ou=CSEE,o=UQ,c=AU" - print "Adding", repr(dn) + print("Adding", repr(dn)) l.add_s(dn, [ ("objectclass",["organizationalUnit"]), @@ -32,7 +33,7 @@ # dn = "cn=David Leonard,ou=CSEE,o=UQ,c=AU" -print "Updating", repr(dn) +print("Updating", repr(dn)) try: l.delete_s(dn) @@ -100,7 +101,7 @@ _ldap.SCOPE_SUBTREE, "objectclass=*", ) -print res +print(res) l.unbind() diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index 804d12f4..aa88f67e 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 0beabb8c..0e72e922 100644 --- a/Doc/ldap-resiter.rst +++ b/Doc/ldap-resiter.rst @@ -46,4 +46,4 @@ processing them in a for-loop. for res_type,res_data,res_msgid,res_controls in l.allresults(msg_id): for dn,entry in res_data: # process dn and entry - print dn,entry['objectClass'] + print(dn,entry['objectClass']) diff --git a/Doc/ldap.rst b/Doc/ldap.rst index e983f647..5160f30b 100644 --- a/Doc/ldap.rst +++ b/Doc/ldap.rst @@ -1169,6 +1169,6 @@ subtree search. [('cn=Fred Feuerstein,ou=Testing,dc=stroeder,dc=de', {'cn': ['Fred Feuerstein']})] >>> r = l.search_s('ou=Testing,dc=stroeder,dc=de',ldap.SCOPE_SUBTREE,'(objectClass=*)',['cn','mail']) >>> for dn,entry in r: ->>> print 'Processing',repr(dn) +>>> print('Processing',repr(dn)) >>> handle_ldap_entry(entry) From 17365cc6045b8c7ce9f5f3292ac592f8dc8e5448 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 21:17:56 +0100 Subject: [PATCH 095/121] 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 04506b36..9e23f1cf 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 457d79c1..d0f82918 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 71722b08..c803a1de 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 88d2d894..2045f50c 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 97720b07..cf6b2ac9 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 b2300ada..3bd59e6d 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 337c5e99..91909a3a 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 479a626d..e4c62e8b 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 7ff75bf1..667221c1 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 9f650d2d..4d350f02 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 ef8bcec1..79c8a837 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 56d611b9..932fa536 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 ee7b575b..7108c632 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 6ddab54c..c60d42b7 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 805b6e34..a1994626 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 99a0dc41..3612a385 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 86b8ac94..714d01c1 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 cac661ec..7c1417b3 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 196a9f3e..5773ff2d 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 3da213f7..76701cc8 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 096/121] 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 9e23f1cf..68d3643e 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 6f4bf1ec..a853500d 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 3612a385..fbc33c5b 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 714d01c1..318f46ce 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 c8812f28..dea802da 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 097/121] 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 07832efb..fdfba4b5 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 00000000..de0e110c --- /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 a1994626..970f0147 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 8471954c..c3bb702e 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 fbc33c5b..4d42b19d 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 bbd6929f..366e177d 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 318f46ce..76593588 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 2f986722..6bb43f08 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 c87b752d..407538ff 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 6bbf595b..a475a35e 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 098/121] 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 970f0147..e83287d5 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 c3bb702e..300981ea 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 099/121] 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 c8a1b121..dab97a67 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 b7b53783..df95a062 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 100/121] 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 76593588..94ffd916 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 101/121] 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 dea802da..00d07266 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 102/121] 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 d8e88bb3..6081b4ba 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 00000000..100b9382 --- /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 103/121] 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 6bb43f08..3c6e5497 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 104/121] 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 3c6e5497..2580ef3b 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 105/121] 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 2580ef3b..1aba8875 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 106/121] 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 7c1417b3..3cbdbe7b 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 407538ff..a68b0327 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 107/121] 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 e69de29b..00000000 From 39ad22ec7185df86ca79781ad96e03c1f2bba898 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Thu, 23 Nov 2017 23:12:06 +0100 Subject: [PATCH 108/121] 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 14992a6e..1f9bf6aa 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 109/121] 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 95387a06..43bde962 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 110/121] 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 43bde962..2eff88ec 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 111/121] 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 3cbdbe7b..cd171b60 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 112/121] 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 a788ae19..46c9c8a9 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 113/121] 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 32985510..00c7b064 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 c60d42b7..b8870378 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 e83287d5..01f00448 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 34d4cb04..fa6f4f5c 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 300981ea..c0391b4c 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 4d42b19d..2a42b4c0 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 94ffd916..82c4e3f8 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 1aba8875..fcd36e1d 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 2eff88ec..ce0ff52a 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 b1186695..73d7f9b0 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -89,7 +89,29 @@ LDAPberval_to_object(const struct berval *bv) Py_INCREF(ret); } else { - ret = PyString_FromStringAndSize(bv->bv_val, bv->bv_len); + ret = PyBytes_FromStringAndSize(bv->bv_val, bv->bv_len); + } + + return ret; +} + +/* + * Same as LDAPberval_to_object, but returns a Unicode PyObject. + * Use when the value is known to be text (for instance a distinguishedName). + * + * Returns a new Python object on success, or NULL on failure. + */ +PyObject * +LDAPberval_to_unicode_object(const struct berval *bv) +{ + PyObject *ret = NULL; + + if (!bv) { + ret = Py_None; + Py_INCREF(ret); + } + else { + ret = PyUnicode_FromStringAndSize(bv->bv_val, bv->bv_len); } return ret; diff --git a/Modules/berval.h b/Modules/berval.h index 514e9f93..2489e456 100644 --- a/Modules/berval.h +++ b/Modules/berval.h @@ -10,5 +10,6 @@ int LDAPberval_from_object(PyObject *obj, struct berval *bv); int LDAPberval_from_object_check(PyObject *obj); void LDAPberval_release(struct berval *bv); PyObject *LDAPberval_to_object(const struct berval *bv); +PyObject *LDAPberval_to_unicode_object(const struct berval *bv); #endif /* __h_berval_ */ diff --git a/Modules/constants.c b/Modules/constants.c index 06c249ad..7ed9e418 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 37319654..e5cb0ee8 100644 --- a/Modules/errors.c +++ b/Modules/errors.c @@ -80,7 +80,7 @@ LDAPerror( LDAP *l, char *msg ) if (info == NULL) return NULL; - str = PyString_FromString(ldap_err2string(errnum)); + str = PyUnicode_FromString(ldap_err2string(errnum)); if (str) PyDict_SetItemString( info, "desc", str ); Py_XDECREF(str); @@ -95,7 +95,7 @@ LDAPerror( LDAP *l, char *msg ) if (ldap_get_option(l, LDAP_OPT_MATCHED_DN, &matched) >= 0 && matched != NULL) { if (*matched != '\0') { - str = PyString_FromString(matched); + str = PyUnicode_FromString(matched); if (str) PyDict_SetItemString( info, "matched", str ); Py_XDECREF(str); @@ -104,13 +104,13 @@ LDAPerror( LDAP *l, char *msg ) } if (errnum == LDAP_REFERRAL) { - str = PyString_FromString(msg); + str = PyUnicode_FromString(msg); if (str) PyDict_SetItemString( info, "info", str ); Py_XDECREF(str); } else if (ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error) >= 0) { if (error != NULL && *error != '\0') { - str = PyString_FromString(error); + str = PyUnicode_FromString(error); if (str) PyDict_SetItemString( info, "info", str ); Py_XDECREF(str); diff --git a/Modules/functions.c b/Modules/functions.c index b2dbf83b..ffb6765c 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -76,9 +76,9 @@ l_ldap_str2dn( PyObject* unused, PyObject *args ) LDAPAVA *ava = rdn[j]; PyObject *tuple; - tuple = Py_BuildValue("(O&O&i)", - LDAPberval_to_object, &ava->la_attr, - LDAPberval_to_object, &ava->la_value, + tuple = Py_BuildValue("(O&O&i)", + LDAPberval_to_unicode_object, &ava->la_attr, + LDAPberval_to_unicode_object, &ava->la_value, ava->la_flags & ~(LDAP_AVA_FREE_ATTR|LDAP_AVA_FREE_VALUE)); if (!tuple) { Py_DECREF(rdnlist); diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index 0bf86a1d..f3dc92a2 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -102,13 +102,13 @@ Tuple_to_LDAPControl( PyObject* tup ) berbytes.bv_len = 0; berbytes.bv_val = NULL; } - else if (PyString_Check(bytes)) { - berbytes.bv_len = PyString_Size(bytes); - berbytes.bv_val = PyString_AsString(bytes); + else if (PyBytes_Check(bytes)) { + berbytes.bv_len = PyBytes_Size(bytes); + berbytes.bv_val = PyBytes_AsString(bytes); } else { PyErr_SetObject(PyExc_TypeError, Py_BuildValue("sO", - "expected a string", bytes)); + "expected bytes", bytes)); LDAPControl_DEL(lc); return NULL; } diff --git a/Modules/message.c b/Modules/message.c index 33127b68..1a289dbd 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -49,6 +49,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi BerElement *ber = NULL; PyObject* entrytuple; PyObject* attrdict; + PyObject* pydn; dn = ldap_get_dn( ld, entry ); if (dn == NULL) { @@ -91,22 +92,26 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi attr = ldap_next_attribute( ld, entry, ber ) ) { PyObject* valuelist; + PyObject* pyattr; + pyattr = PyUnicode_FromString(attr); + struct berval ** bvals = ldap_get_values_len( ld, entry, attr ); /* Find which list to append to */ - if ( PyMapping_HasKeyString( attrdict, attr ) ) { - valuelist = PyMapping_GetItemString( attrdict, attr ); + if ( PyDict_Contains( attrdict, pyattr ) ) { + valuelist = PyDict_GetItem( attrdict, pyattr ); } else { valuelist = PyList_New(0); - if (valuelist != NULL && PyMapping_SetItemString(attrdict, - attr, valuelist) == -1) { + if (valuelist != NULL && PyDict_SetItem(attrdict, + pyattr, valuelist) == -1) { Py_DECREF(valuelist); valuelist = NULL; /* catch error later */ } } if (valuelist == NULL) { + Py_DECREF(pyattr); Py_DECREF(attrdict); Py_DECREF(result); if (ber != NULL) @@ -125,6 +130,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi valuestr = LDAPberval_to_object(bvals[i]); if (PyList_Append( valuelist, valuestr ) == -1) { + Py_DECREF(pyattr); Py_DECREF(attrdict); Py_DECREF(result); Py_DECREF(valuestr); @@ -141,15 +147,25 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi } ldap_value_free_len(bvals); } + Py_DECREF(pyattr); Py_DECREF( valuelist ); ldap_memfree(attr); } + pydn = PyUnicode_FromString(dn); + if (pydn == NULL) { + Py_DECREF(result); + ldap_msgfree( m ); + ldap_memfree(dn); + return NULL; + } + if (add_ctrls) { - entrytuple = Py_BuildValue("(sOO)", dn, attrdict, pyctrls); + entrytuple = Py_BuildValue("(OOO)", pydn, attrdict, pyctrls); } else { - entrytuple = Py_BuildValue("(sO)", dn, attrdict); + entrytuple = Py_BuildValue("(OO)", pydn, attrdict); } + Py_DECREF(pydn); ldap_memfree(dn); Py_DECREF(attrdict); Py_XDECREF(pyctrls); @@ -191,7 +207,8 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi if (refs) { Py_ssize_t i; for (i=0; refs[i] != NULL; i++) { - PyObject *refstr = PyString_FromString(refs[i]); + /* A referal is a distinguishedName => unicode */ + PyObject *refstr = PyUnicode_FromString(refs[i]); PyList_Append(reflist, refstr); Py_DECREF(refstr); } @@ -218,6 +235,7 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi PyObject* valtuple; PyObject *valuestr; char *retoid = 0; + PyObject *pyoid; struct berval *retdata = 0; if (ldap_parse_intermediate( ld, entry, &retoid, &retdata, &serverctrls, 0 ) != LDAP_SUCCESS) { @@ -240,10 +258,17 @@ LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, int add_intermedi valuestr = LDAPberval_to_object(retdata); ber_bvfree( retdata ); - valtuple = Py_BuildValue("(sOO)", retoid, + pyoid = PyUnicode_FromString(retoid); + ldap_memfree( retoid ); + if (pyoid == NULL) { + Py_DECREF(result); + ldap_msgfree( m ); + return NULL; + } + valtuple = Py_BuildValue("(OOO)", pyoid, valuestr ? valuestr : Py_None, pyctrls); - ldap_memfree( retoid ); + Py_DECREF(pyoid); Py_DECREF(valuestr); Py_XDECREF(pyctrls); PyList_Append(result, valtuple); diff --git a/Modules/options.c b/Modules/options.c index a437ca46..7cf996bf 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -204,7 +204,7 @@ LDAP_get_option(LDAPObject *self, int option) extensions = PyTuple_New(num_extensions); for (i = 0; i < num_extensions; i++) PyTuple_SET_ITEM(extensions, i, - PyString_FromString(apiinfo.ldapai_extensions[i])); + PyUnicode_FromString(apiinfo.ldapai_extensions[i])); /* return api info as a dictionary */ v = Py_BuildValue("{s:i, s:i, s:i, s:s, s:i, s:O}", @@ -321,7 +321,7 @@ LDAP_get_option(LDAPObject *self, int option) Py_INCREF(Py_None); return Py_None; } - v = PyString_FromString(strval); + v = PyUnicode_FromString(strval); ldap_memfree(strval); return v; diff --git a/Tests/t_cext.py b/Tests/t_cext.py index cd171b60..d4740d0c 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + import os import unittest from slapdtest import SlapdTestCase @@ -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 97274f37..459c1dcf 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + # from Python's standard lib import unittest @@ -27,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 8f39c675..831c0631 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 5773ff2d..d55d0181 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 a68b0327..2be03f63 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 76701cc8..adf0d262 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -5,6 +5,8 @@ See https://www.python-ldap.org/ for details. """ +from __future__ import unicode_literals + # from Python's standard lib import unittest import textwrap @@ -184,7 +186,7 @@ def test_folded(self): value attrib2: %s - """ % (b'asdf.'*20), [ + """ % ('asdf.'*20), [ ( 'cn=x,cn=y,cn=z', { @@ -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 114/121] 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 1ec232cb..0eea1d92 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 115/121] 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 df95a062..1ca8d208 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 00000000..373688b7 --- /dev/null +++ b/Tests/t_bind.py @@ -0,0 +1,82 @@ +from __future__ import unicode_literals + +import sys + +if sys.version_info[0] <= 2: + PY2 = True + text_type = unicode +else: + PY2 = False + text_type = str + +import ldap, unittest +from slapdtest import SlapdObject +from ldap.ldapobject import LDAPObject + +server = None + + +class TestBinds(unittest.TestCase): + + def setUp(self): + global server + if server is None: + server = SlapdObject() + server.start() + + self.server = server + self.unicode_val = "abc\U0001f498def" + self.unicode_val_bytes = self.unicode_val.encode('utf-8') + + self.dn_unicode = "CN=" + self.unicode_val + self.dn_bytes = self.dn_unicode.encode('utf-8') + + def _get_ldapobject(self, bytes_mode=None): + l = LDAPObject(self.server.ldap_uri, bytes_mode=bytes_mode) + l.protocol_version = 3 + l.set_option(ldap.OPT_REFERRALS,0) + return l + + def test_simple_bind(self): + l = self._get_ldapobject(False) + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_unicode, self.unicode_val) + + def test_unicode_bind(self): + l = self._get_ldapobject(False) + l.simple_bind(self.dn_unicode, "ascii") + + l = self._get_ldapobject(False) + l.simple_bind("CN=user", self.unicode_val) + + @unittest.skipUnless(PY2, "no bytes_mode under Py3") + def test_unicode_bind_bytesmode(self): + l = self._get_ldapobject(True) + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_unicode, self.unicode_val_bytes) + + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_bytes, self.unicode_val) + + # Works when encoded to UTF-8 + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_bytes, self.unicode_val_bytes) + + def test_unicode_bind_no_bytesmode(self): + l = self._get_ldapobject(False) + with self.assertRaises(TypeError): + l.simple_bind_s(self.dn_bytes, self.unicode_val) + + # Works fine in Python 3 because 'cred' (the password) is read in + # using the "s#" format which, unlike "s", accepts either a str + # (unicode) *or* bytes. + # + # with self.assertRaises(TypeError): + # l.simple_bind_s(self.dn_unicode, self.unicode_val_bytes) + + with self.assertRaises(ldap.INVALID_CREDENTIALS): + l.simple_bind_s(self.dn_unicode, self.unicode_val) + + +if __name__ == '__main__': + unittest.main() From 2949e780efe25ae65f9e893a952d3e85abf73972 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:47:14 +0100 Subject: [PATCH 116/121] 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 1ca8d208..46b38618 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 00000000..9aee43e9 --- /dev/null +++ b/Tests/t_edit.py @@ -0,0 +1,89 @@ +from __future__ import unicode_literals + +import sys + +if sys.version_info[0] <= 2: + PY2 = True + text_type = unicode +else: + PY2 = False + text_type = str + +import ldap, unittest +from slapdtest import SlapdObject + +from ldap.ldapobject import LDAPObject + +server = None + + +class EditionTests(unittest.TestCase): + + def setUp(self): + global server + if server is None: + server = SlapdObject() + server.start() + base = server.suffix + suffix_dc = base.split(',')[0][3:] + + # insert some Foo* objects via ldapadd + server.ldapadd("\n".join([ + 'dn: '+server.suffix, + 'objectClass: dcObject', + 'objectClass: organization', + 'dc: '+suffix_dc, + 'o: '+suffix_dc, + '', + 'dn: '+server.root_dn, + 'objectClass: applicationProcess', + 'cn: '+server.root_cn, + '', + "dn: cn=Foo1,"+base, + "objectClass: organizationalRole", + "cn: Foo1", + "", + "dn: cn=Foo2,"+base, + "objectClass: organizationalRole", + "cn: Foo2", + "", + "dn: cn=Foo3,"+base, + "objectClass: organizationalRole", + "cn: Foo3", + "", + "dn: ou=Container,"+base, + "objectClass: organizationalUnit", + "ou: Container", + "", + "dn: cn=Foo4,ou=Container,"+base, + "objectClass: organizationalRole", + "cn: Foo4", + "", + ])+"\n") + + l = LDAPObject(server.ldap_uri, bytes_mode=False) + l.protocol_version = 3 + l.set_option(ldap.OPT_REFERRALS,0) + l.simple_bind_s(server.root_dn, + server.root_pw) + self.ldap = l + self.server = server + + def test_add_object(self): + base = self.server.suffix + dn = "cn=Added,ou=Container," + base + self.ldap.add_ext_s(dn, [ + ("objectClass", [b'organizationalRole']), + ("cn", [b'Added']), + ]) + + # Lookup the object + result = self.ldap.search_s(base, ldap.SCOPE_SUBTREE, '(cn=Added)', ['*']) + self.assertEqual(result, [ + ("cn=Added,ou=Container," + base, + {'cn': [b'Added'], 'objectClass': [b'organizationalRole']}), + ]) + + +if __name__ == '__main__': + unittest.main() From ebcdaae35bb126dd94bafd5d2d82f1460c1562b0 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:48:30 +0100 Subject: [PATCH 117/121] 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 de6421a4..83ed0061 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -10,6 +10,7 @@ import ldif import ldap.schema +from ldap.schema.models import ObjectClass TEST_SUBSCHEMA_FILES = ( 'Tests/ldif/subschema-ipa.demo1.freeipa.org.ldif', @@ -30,6 +31,15 @@ def test_subschema_file(self): _, subschema_subentry = ldif_parser.all_records[0] sub_schema = ldap.schema.SubSchema(subschema_subentry) + # Smoke-check for listall() and attribute_types() + for objclass in sub_schema.listall(ObjectClass): + must, may = sub_schema.attribute_types([objclass]) + + for oid, attributetype in must.items(): + self.assertEqual(attributetype.oid, oid) + for oid, attributetype in may.items(): + self.assertEqual(attributetype.oid, oid) + if __name__ == '__main__': unittest.main() From eb06c59afa2ca0c440db8f9e9c2181f3931b4c17 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:51:29 +0100 Subject: [PATCH 118/121] 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 00000000..542377d8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,25 @@ +language: python + +python: +- '2.7' +- '3.3' +- '3.4' +- '3.5' +- '3.6' +# Note: when updating Python versions, also change setup.py and tox.ini + +sudo: false + +cache: pip + +addons: + apt: + packages: + - ldap-utils + - slapd + +install: + - pip install "pip>=7.1.0" + - pip install tox-travis tox + +script: tox diff --git a/setup.py b/setup.py index a475a35e..af6443bd 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 d11ce1c7..61a6aed9 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,8 @@ # and then run "tox" from this directory. [tox] -envlist = py27 +# Note: when updating Python versions, also change setup.py and .travis.yml +envlist = py27,py33,py34,py35,py36 [testenv] commands = {envpython} setup.py test From 9608ead48b756b514f8a1e65135cd9b091d78683 Mon Sep 17 00:00:00 2001 From: pyldap contributors Date: Fri, 24 Nov 2017 10:53:46 +0100 Subject: [PATCH 119/121] 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 10dafc93..59816b72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ optimize = 1 provides = python-ldap requires = python libldap-2_4 vendor = python-ldap project -packager = Michael Ströder +packager = python-ldap team distribution_name = openSUSE 11.x release = 1 doc_files = CHANGES README INSTALL TODO Demo/ diff --git a/setup.py b/setup.py index af6443bd..89351b5c 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 120/121] 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 6081b4ba..431559dd 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 121/121] Infra: Update CHANGELOG --- CHANGES | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index a8ad44a8..113eb275 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 pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy