From 26c2564a6a1feb75a12a3a268013010fd8153d29 Mon Sep 17 00:00:00 2001 From: stroeder Date: Thu, 16 Nov 2017 16:08:57 +0000 Subject: [PATCH 01/64] 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 b706563..9534aeb 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 39a572b..da891b3 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 9b6f252..fa072c9 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 d00042c..c4ac4e8 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 5b438ba..4381ae0 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 02/64] 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 9534aeb..32b1c7c 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 6add784..e1d79d3 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 e03ca1e..af83e46 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 03/64] 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 c1c5ef1..c8a1b12 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 04/64] 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 b706b8e..0000000 --- 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 4d76b34..0000000 --- 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 962d763..808d4eb 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 05/64] 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 e29e20d..1ec232c 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 06/64] 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 a570220..c11bf72 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 07/64] 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 4223735..a0adc3f 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 08/64] 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 808d4eb..7ce8d9d 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 09/64] 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 7899f04..5d4955d 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 4b3d931..74d107b 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 10/64] 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 32b1c7c..8c6dde4 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 11/64] 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 b4f60eb..c3522ac 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 dd61320..570fdc1 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 12/64] 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 8c6dde4..f4be1fc 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 94a7217..93a3a2a 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 13/64] 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 f4be1fc..32550b6 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 2c0ec1d..06c249a 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 c11bf72..dc954d4 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 7ce8d9d..841ee55 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 14/64] 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 dc954d4..a788ae1 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 15/64] 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 383705c..2e84f48 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 16/64] 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 32550b6..15c4d0e 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 f8e7182..804d12f 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 0d1ac40..9aff147 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 c4ac4e8..8b1cd28 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 841ee55..6bbf595 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 17/64] 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 4898f76..4dd1e02 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 18/64] 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 fa072c9..dc81dd9 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 19/64] 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 15c4d0e..c5c3320 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 4dd1e02..3da213f 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 20/64] 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 c5c3320..aad549c 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 8b1cd28..e06cbd3 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 21/64] docstring cosmetics --- Lib/slapdtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/slapdtest.py b/Lib/slapdtest.py index 4381ae0..3586643 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 22/64] 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 dc81dd9..1fa9b1c 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 23/64] 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 9aff147..c2cca5d 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 24/64] 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 ede7e21..20958c0 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 25/64] 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 e06cbd3..f68838e 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 26/64] 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 a80f238..b5e04c2 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 27/64] 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 a1f8fc5..68ac6f9 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 28/64] 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 3f9e8e4..ac56fd3 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 29/64] 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 68ac6f9..a9e6dee 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 ac56fd3..c8812f2 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 30/64] 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 a9e6dee..d36f8c3 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 31/64] 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 2e84f48..208fe40 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 b5e04c2..99a0dc4 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 32/64] 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 81824e4..7f3d7c3 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 33/64] 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 70ecec7..33ce36a 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 34/64] 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 208fe40..8471954 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 35/64] 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 c2cca5d..1c9c1dc 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 36/64] 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 8cc4aa5..9671762 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 05e439b..11ed7b6 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 498b857..62cb391 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 3394524..ff9fe5a 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 7f3d7c3..0dd4940 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 33ce36a..f4e61da 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 1c9c1dc..99e4a18 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 d8c1368..bb72618 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 1fa9b1c..bbd6929 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 f68838e..86b8ac9 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 3586643..2f98672 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 37/64] 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 af83e46..ec32b16 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 38/64] 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 ec32b16..dc3221e 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 39/64] (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 dc3221e..9defe7c 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 40/64] 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 9defe7c..dfe2412 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 41/64] 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 aad549c..2c280ac 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 f4e61da..e374858 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 42/64] 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 d36f8c3..07832ef 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 43/64] 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 99e4a18..6f4bf1e 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 44/64] 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 d955c2e..4db961e 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 45/64] 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 daab8ab..1d70058 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 ea763fa..6ddab54 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 81438cc..77d8ee2 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 46/64] 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 77d8ee2..d67153d 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 47/64] 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 d67153d..34d4cb0 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 48/64] 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 2c280ac..7aaef0f 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 043cc70..9a3c54a 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 49/64] 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 d78f070..d896e4c 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 1d70058..3298551 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 50/64] 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 d896e4c..c22a64c 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 51/64] 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 7aaef0f..1d3bba0 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 e374858..855dabf 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 52/64] 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 9a3c54a..97274f3 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 53/64] 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 ab7c509..9c322de 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 54/64] 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 bb72618..dc912eb 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 55/64] 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 93a3a2a..8442726 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 56/64] 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 1177d18..1454eef 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 57/64] 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 855dabf..805b6e3 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 58/64] 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 1d3bba0..6bd4f73 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 0000000..72cbce3 --- /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 59/64] 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 dfe2412..196a9f3 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 60/64] 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 6bd4f73..6f96481 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 3291379..95387a0 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 a0adc3f..8cd6fc3 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 61/64] prepare release 2.5.2 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 6f96481..b3ef0d5 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 62/64] 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 e1d79d3..cac661e 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 63/64] 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 72cbce3..b9640fc 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 64/64] 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 b9640fc..8f39c67 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 $ """