From 4fc9038290d874cc1da6b611159c634a000aa1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 21:45:03 +0100 Subject: [PATCH 01/73] slapdtest: Rework waiting for SlapdObject startup This gives a chance to SlapdObject to start faster. Notably it does not start by sleeping 1.5s anymore. https://github.com/python-ldap/python-ldap/pull/390 --- Lib/slapdtest/_slapdtest.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 6784cf12..141f459b 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -208,9 +208,6 @@ class SlapdObject(object): BIN_PATH = os.environ.get('BIN', os.environ.get('PATH', os.defpath)) SBIN_PATH = os.environ.get('SBIN', _add_sbin(BIN_PATH)) - # time in secs to wait before trying to access slapd via LDAP (again) - _start_sleep = 1.5 - # create loggers once, multiple calls mess up refleak tests _log = combined_logger('python-ldap-test') @@ -418,20 +415,22 @@ def _start_slapd(self): self._log.info('starting slapd: %r', ' '.join(slapd_args)) self._proc = subprocess.Popen(slapd_args) # Waits until the LDAP server socket is open, or slapd crashed + deadline = time.monotonic() + 10 # no cover to avoid spurious coverage changes, see # https://github.com/python-ldap/python-ldap/issues/127 - for _ in range(10): # pragma: no cover + while True: # pragma: no cover if self._proc.poll() is not None: self._stopped() raise RuntimeError("slapd exited before opening port") - time.sleep(self._start_sleep) try: self._log.debug( "slapd connection check to %s", self.default_ldap_uri ) self.ldapwhoami() except RuntimeError: - pass + if time.monotonic() >= deadline: + break + time.sleep(0.2) else: return raise RuntimeError("slapd did not start properly") From 3484d574d9c9daa6fdda1d4b0e0a949021b48564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 21:54:44 +0100 Subject: [PATCH 02/73] Remove Python 2 syntax (#383) https://github.com/python-ldap/python-ldap/pull/383 --- .gitignore | 3 ++ Demo/Lib/ldap/async/deltree.py | 2 -- Demo/Lib/ldapurl/urlsearch.py | 1 - Demo/initialize.py | 1 - Demo/ldapcontrols.py | 1 - Demo/ldapurl_search.py | 2 -- Demo/matchedvalues.py | 3 +- Demo/options.py | 2 -- Demo/page_control.py | 3 -- Demo/paged_search_ext_s.py | 1 - Demo/passwd_ext_op.py | 1 - Demo/pyasn1/dds.py | 2 -- Demo/pyasn1/derefcontrol.py | 1 - Demo/pyasn1/noopsearch.py | 2 -- Demo/pyasn1/ppolicy.py | 2 -- Demo/pyasn1/psearch.py | 2 -- Demo/pyasn1/readentrycontrol.py | 1 - Demo/pyasn1/sessiontrack.py | 2 -- Demo/pyasn1/syncrepl.py | 2 -- Demo/rename.py | 1 - Demo/resiter.py | 1 - Demo/sasl_bind.py | 1 - Demo/schema.py | 1 - Demo/schema_tree.py | 7 ++-- Demo/simple.py | 3 +- Demo/simplebrowse.py | 1 - Doc/conf.py | 1 - Lib/ldap/__init__.py | 6 ++-- Lib/ldap/asyncsearch.py | 2 +- Lib/ldap/constants.py | 11 +++---- Lib/ldap/controls/__init__.py | 3 +- Lib/ldap/controls/deref.py | 1 - Lib/ldap/controls/libldap.py | 3 +- Lib/ldap/controls/openldap.py | 1 - Lib/ldap/controls/pagedresults.py | 1 - Lib/ldap/controls/ppolicy.py | 1 - Lib/ldap/controls/psearch.py | 1 - Lib/ldap/controls/pwdpolicy.py | 1 - Lib/ldap/controls/readentry.py | 1 - Lib/ldap/controls/sessiontrack.py | 1 - Lib/ldap/controls/simple.py | 1 - Lib/ldap/controls/sss.py | 1 - Lib/ldap/controls/vlv.py | 1 - Lib/ldap/dn.py | 2 +- Lib/ldap/extop/__init__.py | 4 +-- Lib/ldap/extop/dds.py | 1 - Lib/ldap/extop/passwd.py | 1 - Lib/ldap/filter.py | 2 +- Lib/ldap/functions.py | 2 +- Lib/ldap/ldapobject.py | 28 ++++++++-------- Lib/ldap/logger.py | 3 +- Lib/ldap/pkginfo.py | 3 +- Lib/ldap/schema/models.py | 24 +++++++------- Lib/ldap/schema/subentry.py | 8 ++--- Lib/ldap/syncrepl.py | 1 - Lib/ldapurl.py | 22 ++++++------- Lib/slapdtest/__init__.py | 1 - Lib/slapdtest/_slapdtest.py | 3 +- Tests/__init__.py | 2 -- Tests/t_cext.py | 9 +++-- Tests/t_cidict.py | 1 - Tests/t_edit.py | 2 +- Tests/t_ldap_dn.py | 1 - Tests/t_ldap_filter.py | 1 - Tests/t_ldap_functions.py | 1 - Tests/t_ldap_modlist.py | 5 ++- Tests/t_ldap_options.py | 4 +-- Tests/t_ldap_sasl.py | 7 ++-- Tests/t_ldap_schema_subentry.py | 1 - Tests/t_ldap_schema_tokenizer.py | 1 - Tests/t_ldap_syncrepl.py | 11 +++---- Tests/t_ldapobject.py | 55 +++++++++++++++---------------- Tests/t_ldapurl.py | 5 ++- Tests/t_ldif.py | 1 - 74 files changed, 114 insertions(+), 184 deletions(-) diff --git a/.gitignore b/.gitignore index 5f6d0425..bab21878 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ PKG-INFO # generated in the sample workflow /__venv__/ + +# test dirs +python-ldap-test-* diff --git a/Demo/Lib/ldap/async/deltree.py b/Demo/Lib/ldap/async/deltree.py index d3fc6203..9db52c84 100644 --- a/Demo/Lib/ldap/async/deltree.py +++ b/Demo/Lib/ldap/async/deltree.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ldap,ldap.async class DeleteLeafs(ldap.async.AsyncSearchHandler): diff --git a/Demo/Lib/ldapurl/urlsearch.py b/Demo/Lib/ldapurl/urlsearch.py index b293aa20..c58a1954 100644 --- a/Demo/Lib/ldapurl/urlsearch.py +++ b/Demo/Lib/ldapurl/urlsearch.py @@ -3,7 +3,6 @@ No output of LDAP data is produced except trace output. """ -from __future__ import print_function import sys,getpass,ldap,ldapurl try: diff --git a/Demo/initialize.py b/Demo/initialize.py index 952b3f4b..ab78cdb8 100644 --- a/Demo/initialize.py +++ b/Demo/initialize.py @@ -7,7 +7,6 @@ ldaps://localhost:1391 (LDAP over SSL) ldapi://%2ftmp%2fopenldap2 (domain socket /tmp/openldap2) """ -from __future__ import print_function import sys,os,ldap diff --git a/Demo/ldapcontrols.py b/Demo/ldapcontrols.py index a5ba8d34..eec86b4c 100644 --- a/Demo/ldapcontrols.py +++ b/Demo/ldapcontrols.py @@ -1,4 +1,3 @@ -from __future__ import print_function import ldap,ldapurl,pprint from ldap.controls import LDAPControl,BooleanControl diff --git a/Demo/ldapurl_search.py b/Demo/ldapurl_search.py index 07ffbca5..614ba1af 100644 --- a/Demo/ldapurl_search.py +++ b/Demo/ldapurl_search.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import sys,pprint,ldap from ldap.ldapobject import LDAPObject diff --git a/Demo/matchedvalues.py b/Demo/matchedvalues.py index 7a6e7b6b..bbc2a1bc 100644 --- a/Demo/matchedvalues.py +++ b/Demo/matchedvalues.py @@ -27,7 +27,6 @@ # Matched values control: (mail=*@example.org) # dn: uid=jsmith,ou=People,dc=example,dc=com # mail: jsmith@example.org -from __future__ import print_function import ldap from ldap.controls import MatchedValuesControl @@ -37,7 +36,7 @@ def print_result(search_result): print("dn: %s" % search_result[n][0]) for attr in search_result[n][1].keys(): for i in range(len(search_result[n][1][attr])): - print("%s: %s" % (attr, search_result[n][1][attr][i])) + print("{}: {}".format(attr, search_result[n][1][attr][i])) print diff --git a/Demo/options.py b/Demo/options.py index 28a04374..7a8ee9db 100644 --- a/Demo/options.py +++ b/Demo/options.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import ldap host="localhost:1390" diff --git a/Demo/page_control.py b/Demo/page_control.py index 8238ede3..b92cbf86 100644 --- a/Demo/page_control.py +++ b/Demo/page_control.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import print_function - url = "ldap://localhost:1390" base = "dc=stroeder,dc=de" search_flt = r'(objectClass=*)' diff --git a/Demo/paged_search_ext_s.py b/Demo/paged_search_ext_s.py index d0f82918..3a1a4acd 100644 --- a/Demo/paged_search_ext_s.py +++ b/Demo/paged_search_ext_s.py @@ -1,4 +1,3 @@ -from __future__ import print_function url = "ldap://localhost:1390/" base = "dc=stroeder,dc=de" search_flt = r'(objectClass=*)' diff --git a/Demo/passwd_ext_op.py b/Demo/passwd_ext_op.py index cc5d22cd..6c695482 100644 --- a/Demo/passwd_ext_op.py +++ b/Demo/passwd_ext_op.py @@ -1,7 +1,6 @@ """ Example showing the use of the password extended operation. """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/dds.py b/Demo/pyasn1/dds.py index c803a1de..19270603 100644 --- a/Demo/pyasn1/dds.py +++ b/Demo/pyasn1/dds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Dynamic Entries (see RFC 2589) @@ -8,7 +7,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function from ldap.extop.dds import RefreshRequest,RefreshResponse diff --git a/Demo/pyasn1/derefcontrol.py b/Demo/pyasn1/derefcontrol.py index 0e7153de..9565a9e8 100644 --- a/Demo/pyasn1/derefcontrol.py +++ b/Demo/pyasn1/derefcontrol.py @@ -3,7 +3,6 @@ This sample script demonstrates the use of the dereference control (see https://tools.ietf.org/html/draft-masarati-ldap-deref) """ -from __future__ import print_function import pprint,ldap,ldap.modlist,ldap.resiter diff --git a/Demo/pyasn1/noopsearch.py b/Demo/pyasn1/noopsearch.py index 2045f50c..a239c0e7 100644 --- a/Demo/pyasn1/noopsearch.py +++ b/Demo/pyasn1/noopsearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for counting searching with OpenLDAP's no-op control @@ -9,7 +8,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/ppolicy.py b/Demo/pyasn1/ppolicy.py index cf6b2ac9..c143bf16 100644 --- a/Demo/pyasn1/ppolicy.py +++ b/Demo/pyasn1/ppolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Password Policy Controls (see https://tools.ietf.org/html/draft-behera-ldap-password-policy) @@ -9,7 +8,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/psearch.py b/Demo/pyasn1/psearch.py index 3bd59e6d..2703a253 100644 --- a/Demo/pyasn1/psearch.py +++ b/Demo/pyasn1/psearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Demo script for Persistent Search Control (see https://tools.ietf.org/html/draft-ietf-ldapext-psearch) @@ -10,7 +9,6 @@ pyasn1-modules python-ldap 2.4+ """ -from __future__ import print_function import sys,ldap,ldapurl,getpass diff --git a/Demo/pyasn1/readentrycontrol.py b/Demo/pyasn1/readentrycontrol.py index a857be22..b3ea6e81 100644 --- a/Demo/pyasn1/readentrycontrol.py +++ b/Demo/pyasn1/readentrycontrol.py @@ -4,7 +4,6 @@ Originally contributed by Andreas Hasenack """ -from __future__ import print_function import pprint,ldap,ldap.modlist diff --git a/Demo/pyasn1/sessiontrack.py b/Demo/pyasn1/sessiontrack.py index 58a34e82..491172c0 100644 --- a/Demo/pyasn1/sessiontrack.py +++ b/Demo/pyasn1/sessiontrack.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ demo_track_ldap_session.py @@ -8,7 +7,6 @@ https://tools.ietf.org/html/draft-wahl-ldap-session-03 """ -from __future__ import print_function __version__ = '0.1' diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index 7103bc64..f1f24e19 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- """ This script implements a syncrepl consumer which syncs data from an OpenLDAP server to a local (shelve) database. @@ -8,7 +7,6 @@ The bound user needs read access to the attributes entryDN and entryCSN. """ -from __future__ import print_function # Import modules from Python standard lib import logging diff --git a/Demo/rename.py b/Demo/rename.py index 91d7528f..edb78a80 100644 --- a/Demo/rename.py +++ b/Demo/rename.py @@ -1,4 +1,3 @@ -from __future__ import print_function import ldap from getpass import getpass diff --git a/Demo/resiter.py b/Demo/resiter.py index 96e9c90d..9fc14e41 100644 --- a/Demo/resiter.py +++ b/Demo/resiter.py @@ -4,7 +4,6 @@ See https://www.python-ldap.org for details. """ -from __future__ import print_function import ldap,ldap.resiter diff --git a/Demo/sasl_bind.py b/Demo/sasl_bind.py index 667221c1..8453d087 100644 --- a/Demo/sasl_bind.py +++ b/Demo/sasl_bind.py @@ -1,6 +1,5 @@ # For documentation, see comments in Module/LDAPObject.c and the # ldap.sasl module documentation. -from __future__ import print_function import ldap,ldap.sasl diff --git a/Demo/schema.py b/Demo/schema.py index 03f90a83..bdad5e0c 100644 --- a/Demo/schema.py +++ b/Demo/schema.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys,ldap,ldap.schema schema_attrs = ldap.schema.SCHEMA_ATTRS diff --git a/Demo/schema_tree.py b/Demo/schema_tree.py index bda5f64d..2b182e98 100644 --- a/Demo/schema_tree.py +++ b/Demo/schema_tree.py @@ -4,7 +4,6 @@ Usage: schema_oc_tree.py [--html] [LDAP URL] """ -from __future__ import print_function import sys,getopt,ldap,ldap.schema @@ -28,10 +27,10 @@ def HTMLSchemaTree(schema,se_class,se_tree,se_oid,level): se_obj = schema.get_obj(se_class,se_oid) if se_obj!=None: print(""" -
%s (%s)
+
{} ({})
- %s - """ % (', '.join(se_obj.names),se_obj.oid,se_obj.desc)) + {} + """.format(', '.join(se_obj.names),se_obj.oid,se_obj.desc)) if se_tree[se_oid]: print('
') for sub_se_oid in se_tree[se_oid]: diff --git a/Demo/simple.py b/Demo/simple.py index 5fa04250..c82659c3 100644 --- a/Demo/simple.py +++ b/Demo/simple.py @@ -1,4 +1,3 @@ -from __future__ import print_function import sys,getpass import ldap @@ -86,7 +85,7 @@ ("labeleduri", ["labeleduri"]), ("manager", ["cn=Jaga Indulska"]), ("reports", ["reports"]), - ("jpegPhoto", [open("/www/leonard/leonard.jpg","r").read()]), + ("jpegPhoto", [open("/www/leonard/leonard.jpg").read()]), ("uid", ["leonard"]), ("userPassword", [""]) diff --git a/Demo/simplebrowse.py b/Demo/simplebrowse.py index 9d138003..fd4563ae 100644 --- a/Demo/simplebrowse.py +++ b/Demo/simplebrowse.py @@ -3,7 +3,6 @@ # # simple LDAP server browsing example # -from __future__ import print_function import ldap from traceback import print_exc diff --git a/Doc/conf.py b/Doc/conf.py index e1fb9ee2..0fa396b8 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # python-ldap documentation build configuration file, created by # sphinx-quickstart on Sat Mar 29 15:08:17 2008. diff --git a/Lib/ldap/__init__.py b/Lib/ldap/__init__.py index 8d675733..b1797078 100644 --- a/Lib/ldap/__init__.py +++ b/Lib/ldap/__init__.py @@ -33,7 +33,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') from _ldap import * # call into libldap to initialize it right now LIBLDAP_API_INFO = _ldap.get_option(_ldap.OPT_API_INFO) @@ -83,14 +83,14 @@ def acquire(self): if __debug__: global _trace_level if _trace_level>=self._min_trace_level: - _trace_file.write('***%s.acquire() %s %s\n' % (self.__class__.__name__,repr(self),self._desc)) + _trace_file.write('***{}.acquire() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc)) return self._lock.acquire() def release(self): if __debug__: global _trace_level if _trace_level>=self._min_trace_level: - _trace_file.write('***%s.release() %s %s\n' % (self.__class__.__name__,repr(self),self._desc)) + _trace_file.write('***{}.release() {} {}\n'.format(self.__class__.__name__,repr(self),self._desc)) return self._lock.release() diff --git a/Lib/ldap/asyncsearch.py b/Lib/ldap/asyncsearch.py index 6514dd05..6c6929dd 100644 --- a/Lib/ldap/asyncsearch.py +++ b/Lib/ldap/asyncsearch.py @@ -30,7 +30,7 @@ def __init__(self,receivedResultType,expectedResultTypes): Exception.__init__(self) def __str__(self): - return 'Received wrong result type %s (expected one of %s).' % ( + return 'Received wrong result type {} (expected one of {}).'.format( self.receivedResultType, ', '.join(self.expectedResultTypes), ) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 5e178a17..b6ec0d33 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -13,16 +13,15 @@ # This module cannot import anything from ldap. # When building documentation, it is used to initialize ldap.__init__. -from __future__ import print_function -class Constant(object): +class Constant: """Base class for a definition of an OpenLDAP constant """ def __init__(self, name, optional=False, requirements=(), doc=None): self.name = name if optional: - self_requirement = 'defined(LDAP_{})'.format(self.name) + self_requirement = f'defined(LDAP_{self.name})' requirements = list(requirements) + [self_requirement] self.requirements = requirements self.doc = self.__doc__ = doc @@ -50,7 +49,7 @@ class TLSInt(Int): def __init__(self, *args, **kwargs): requrements = list(kwargs.get('requirements', ())) kwargs['requirements'] = ['HAVE_TLS'] + requrements - super(TLSInt, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class Feature(Constant): @@ -70,7 +69,7 @@ class Feature(Constant): def __init__(self, name, c_feature, **kwargs): - super(Feature, self).__init__(name, **kwargs) + super().__init__(name, **kwargs) self.c_feature = c_feature @@ -392,7 +391,7 @@ def pop_requirement(): if requirement not in current_requirements: current_requirements.append(requirement) print() - print('#if {}'.format(requirement)) + print(f'#if {requirement}') print(definition.c_template.format(self=definition)) diff --git a/Lib/ldap/controls/__init__.py b/Lib/ldap/controls/__init__.py index 811b3be1..73557168 100644 --- a/Lib/ldap/controls/__init__.py +++ b/Lib/ldap/controls/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ controls.py - support classes for LDAP controls @@ -13,7 +12,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap diff --git a/Lib/ldap/controls/deref.py b/Lib/ldap/controls/deref.py index b9994eb6..e5b2a7ec 100644 --- a/Lib/ldap/controls/deref.py +++ b/Lib/ldap/controls/deref.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.deref - classes for (see https://tools.ietf.org/html/draft-masarati-ldap-deref) diff --git a/Lib/ldap/controls/libldap.py b/Lib/ldap/controls/libldap.py index f6ea42c4..9a102379 100644 --- a/Lib/ldap/controls/libldap.py +++ b/Lib/ldap/controls/libldap.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ controls.libldap - LDAP controls wrapper classes with en-/decoding done by OpenLDAP functions @@ -10,7 +9,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap diff --git a/Lib/ldap/controls/openldap.py b/Lib/ldap/controls/openldap.py index 5da2dd3f..24040ed7 100644 --- a/Lib/ldap/controls/openldap.py +++ b/Lib/ldap/controls/openldap.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.openldap - classes for OpenLDAP-specific controls diff --git a/Lib/ldap/controls/pagedresults.py b/Lib/ldap/controls/pagedresults.py index efdd0408..12ca573d 100644 --- a/Lib/ldap/controls/pagedresults.py +++ b/Lib/ldap/controls/pagedresults.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.paged - classes for Simple Paged control (see RFC 2696) diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index 67efe3ad..ebc456ad 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.ppolicy - classes for Password Policy controls (see https://tools.ietf.org/html/draft-behera-ldap-password-policy) diff --git a/Lib/ldap/controls/psearch.py b/Lib/ldap/controls/psearch.py index 002a88e9..32900c8b 100644 --- a/Lib/ldap/controls/psearch.py +++ b/Lib/ldap/controls/psearch.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.psearch - classes for Persistent Search Control (see https://tools.ietf.org/html/draft-ietf-ldapext-psearch) diff --git a/Lib/ldap/controls/pwdpolicy.py b/Lib/ldap/controls/pwdpolicy.py index cf9c1978..54f1a700 100644 --- a/Lib/ldap/controls/pwdpolicy.py +++ b/Lib/ldap/controls/pwdpolicy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.pwdpolicy - classes for Password Policy controls (see https://tools.ietf.org/html/draft-vchu-ldap-pwd-policy) diff --git a/Lib/ldap/controls/readentry.py b/Lib/ldap/controls/readentry.py index 57cefef4..c5db4247 100644 --- a/Lib/ldap/controls/readentry.py +++ b/Lib/ldap/controls/readentry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.readentry - classes for the Read Entry controls (see RFC 4527) diff --git a/Lib/ldap/controls/sessiontrack.py b/Lib/ldap/controls/sessiontrack.py index 9c8a057f..a1fb8b34 100644 --- a/Lib/ldap/controls/sessiontrack.py +++ b/Lib/ldap/controls/sessiontrack.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.sessiontrack - class for session tracking control (see draft-wahl-ldap-session) diff --git a/Lib/ldap/controls/simple.py b/Lib/ldap/controls/simple.py index d4130348..05f6760d 100644 --- a/Lib/ldap/controls/simple.py +++ b/Lib/ldap/controls/simple.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.simple - classes for some very simple LDAP controls diff --git a/Lib/ldap/controls/sss.py b/Lib/ldap/controls/sss.py index 5001987b..e6ee3686 100644 --- a/Lib/ldap/controls/sss.py +++ b/Lib/ldap/controls/sss.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.sss - classes for Server Side Sorting (see RFC 2891) diff --git a/Lib/ldap/controls/vlv.py b/Lib/ldap/controls/vlv.py index 9fea2f03..5fc7ce88 100644 --- a/Lib/ldap/controls/vlv.py +++ b/Lib/ldap/controls/vlv.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.controls.vlv - classes for Virtual List View (see draft-ietf-ldapext-ldapv3-vlv) diff --git a/Lib/ldap/dn.py b/Lib/ldap/dn.py index 886ff877..a9d96846 100644 --- a/Lib/ldap/dn.py +++ b/Lib/ldap/dn.py @@ -7,7 +7,7 @@ import _ldap assert _ldap.__version__==__version__, \ - ImportError('ldap %s and _ldap %s version mismatch!' % (__version__,_ldap.__version__)) + ImportError(f'ldap {__version__} and _ldap {_ldap.__version__} version mismatch!') import ldap.functions diff --git a/Lib/ldap/extop/__init__.py b/Lib/ldap/extop/__init__.py index 39e653a9..dc9aea2f 100644 --- a/Lib/ldap/extop/__init__.py +++ b/Lib/ldap/extop/__init__.py @@ -28,7 +28,7 @@ def __init__(self,requestName,requestValue): self.requestValue = requestValue def __repr__(self): - return '%s(%s,%s)' % (self.__class__.__name__,self.requestName,self.requestValue) + return f'{self.__class__.__name__}({self.requestName},{self.requestValue})' def encodedRequestValue(self): """ @@ -53,7 +53,7 @@ def __init__(self,responseName,encodedResponseValue): self.responseValue = self.decodeResponseValue(encodedResponseValue) def __repr__(self): - return '%s(%s,%s)' % (self.__class__.__name__,self.responseName,self.responseValue) + return f'{self.__class__.__name__}({self.responseName},{self.responseValue})' def decodeResponseValue(self,value): """ diff --git a/Lib/ldap/extop/dds.py b/Lib/ldap/extop/dds.py index 4d156e83..7fab0813 100644 --- a/Lib/ldap/extop/dds.py +++ b/Lib/ldap/extop/dds.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.extop.dds - Classes for Dynamic Entries extended operations (see RFC 2589) diff --git a/Lib/ldap/extop/passwd.py b/Lib/ldap/extop/passwd.py index 0a8346a8..13e9f252 100644 --- a/Lib/ldap/extop/passwd.py +++ b/Lib/ldap/extop/passwd.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.extop.passwd - Classes for Password Modify extended operation (see RFC 3062) diff --git a/Lib/ldap/filter.py b/Lib/ldap/filter.py index 3dba7f74..782737aa 100644 --- a/Lib/ldap/filter.py +++ b/Lib/ldap/filter.py @@ -71,7 +71,7 @@ def time_span_filter( if from_timestamp < 0: from_timestamp = until_timestamp + from_timestamp if from_timestamp > until_timestamp: - raise ValueError('from_timestamp %r must not be greater than until_timestamp %r' % ( + raise ValueError('from_timestamp {!r} must not be greater than until_timestamp {!r}'.format( from_timestamp, until_timestamp )) return ( diff --git a/Lib/ldap/functions.py b/Lib/ldap/functions.py index 8c9dc626..8658db40 100644 --- a/Lib/ldap/functions.py +++ b/Lib/ldap/functions.py @@ -41,7 +41,7 @@ def _ldap_function_call(lock,func,*args,**kwargs): lock.acquire() if __debug__: if ldap._trace_level>=1: - ldap._trace_file.write('*** %s.%s %s\n' % ( + ldap._trace_file.write('*** {}.{} {}\n'.format( '_ldap',func.__name__, pprint.pformat((args,kwargs)) )) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index dcdeea5a..40091ad7 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -114,7 +114,7 @@ def _ldap_call(self,func,*args,**kwargs): self._ldap_object_lock.acquire() if __debug__: if self._trace_level>=1: - self._trace_file.write('*** %s %s - %s\n%s\n' % ( + self._trace_file.write('*** {} {} - {}\n{}\n'.format( repr(self), self._uri, '.'.join((self.__class__.__name__,func.__name__)), @@ -138,7 +138,7 @@ def _ldap_call(self,func,*args,**kwargs): except IndexError: pass if __debug__ and self._trace_level>=2: - self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e))) + self._trace_file.write('=> LDAPError - {}: {}\n'.format(e.__class__.__name__,str(e))) raise else: if __debug__ and self._trace_level>=2: @@ -159,7 +159,7 @@ def __getattr__(self,name): elif name in self.__dict__: return self.__dict__[name] else: - raise AttributeError('%s has no attribute %s' % ( + raise AttributeError('{} has no attribute {}'.format( self.__class__.__name__,repr(name) )) @@ -326,7 +326,7 @@ def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None): except ldap.COMPARE_FALSE: return False raise ldap.PROTOCOL_ERROR( - 'Compare operation returned wrong result: %r' % (ldap_res,) + f'Compare operation returned wrong result: {ldap_res!r}' ) def compare(self,dn,attr,value): @@ -384,7 +384,7 @@ def extop_s(self,extreq,serverctrls=None,clientctrls=None,extop_resp_class=None) if extop_resp_class: respoid,respvalue = res if extop_resp_class.responseName!=respoid: - raise ldap.PROTOCOL_ERROR("Wrong OID in extended response! Expected %s, got %s" % (extop_resp_class.responseName,respoid)) + raise ldap.PROTOCOL_ERROR(f"Wrong OID in extended response! Expected {extop_resp_class.responseName}, got {respoid}") return extop_resp_class(extop_resp_class.responseName,respvalue) else: return res @@ -707,8 +707,8 @@ def search_subschemasubentry_s(self,dn=None): Returns: None or text/bytes depending on bytes_mode. """ - empty_dn = u'' - attrname = u'subschemaSubentry' + empty_dn = '' + attrname = 'subschemaSubentry' if dn is None: dn = empty_dn try: @@ -760,7 +760,7 @@ def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None): """ Returns the sub schema sub entry's data """ - filterstr = u'(objectClass=subschema)' + filterstr = '(objectClass=subschema)' if attrs is None: attrs = SCHEMA_ATTRS try: @@ -797,8 +797,8 @@ def read_rootdse_s(self, filterstr=None, attrlist=None): """ convenience wrapper around read_s() for reading rootDSE """ - base = u'' - attrlist = attrlist or [u'*', u'+'] + base = '' + attrlist = attrlist or ['*', '+'] ldap_rootdse = self.read_s( base, filterstr=filterstr, @@ -811,7 +811,7 @@ def get_naming_contexts(self): returns all attribute values of namingContexts in rootDSE if namingContexts is not present (not readable) then empty list is returned """ - name = u'namingContexts' + name = 'namingContexts' return self.read_rootdse_s( attrlist=[name] ).get(name, []) @@ -923,7 +923,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): while reconnect_counter: counter_text = '%d. (of %d)' % (retry_max-reconnect_counter+1,retry_max) if __debug__ and self._trace_level>=1: - self._trace_file.write('*** Trying %s reconnect to %s...\n' % ( + self._trace_file.write('*** Trying {} reconnect to {}...\n'.format( counter_text,uri )) try: @@ -941,7 +941,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): raise except (ldap.SERVER_DOWN,ldap.TIMEOUT): if __debug__ and self._trace_level>=1: - self._trace_file.write('*** %s reconnect to %s failed\n' % ( + self._trace_file.write('*** {} reconnect to {} failed\n'.format( counter_text,uri )) reconnect_counter = reconnect_counter-1 @@ -952,7 +952,7 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): time.sleep(retry_delay) else: if __debug__ and self._trace_level>=1: - self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % ( + self._trace_file.write('*** {} reconnect to {} successful => repeat last operation\n'.format( counter_text,uri )) self._reconnects_done = self._reconnects_done + 1 diff --git a/Lib/ldap/logger.py b/Lib/ldap/logger.py index 4db961e3..ae66bd08 100644 --- a/Lib/ldap/logger.py +++ b/Lib/ldap/logger.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- """ Helper class for using logging as trace file object """ import logging -class logging_file_class(object): +class logging_file_class: def __init__(self, logging_level): self._logging_level = logging_level diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index d99d2d00..2d88dc07 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- """ meta attributes for packaging which does not import any dependencies """ __version__ = '3.3.0' -__author__ = u'python-ldap project' +__author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index 0ebd61e7..d73420c5 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -69,9 +69,9 @@ def key_attr(self,key,value,quoted=0): 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("'","\\'")) + return " {} '{}'".format(key,value.replace("'","\\'")) else: - return " %s %s" % (key,value) + return f" {key} {value}" else: return "" @@ -84,9 +84,9 @@ def key_list(self,key,values,sep=' ',quoted=0): else: quoted_values = values if len(values)==1: - return ' %s %s' % (key,quoted_values[0]) + return ' {} {}'.format(key,quoted_values[0]) else: - return ' %s ( %s )' % (key,sep.join(quoted_values)) + return ' {} ( {} )'.format(key,sep.join(quoted_values)) def __str__(self): result = [str(self.oid)] @@ -133,7 +133,7 @@ class ObjectClass(SchemaElement): implementations to indicate the source of the associated schema element """ - schema_attribute = u'objectClasses' + schema_attribute = 'objectClasses' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -240,7 +240,7 @@ class AttributeType(SchemaElement): implementations to indicate the source of the associated schema element """ - schema_attribute = u'attributeTypes' + schema_attribute = 'attributeTypes' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -334,7 +334,7 @@ class LDAPSyntax(SchemaElement): Integer flag (0 or 1) indicating whether the attribute type is marked as not human-readable (X-NOT-HUMAN-READABLE) """ - schema_attribute = u'ldapSyntaxes' + schema_attribute = 'ldapSyntaxes' token_defaults = { 'DESC':(None,), 'X-NOT-HUMAN-READABLE':(None,), @@ -383,7 +383,7 @@ class MatchingRule(SchemaElement): OID of the LDAP syntax this matching rule is usable with (string, or None if missing) """ - schema_attribute = u'matchingRules' + schema_attribute = 'matchingRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -429,7 +429,7 @@ class MatchingRuleUse(SchemaElement): NAMEs or OIDs of attribute types for which this matching rule is used (tuple of strings) """ - schema_attribute = u'matchingRuleUse' + schema_attribute = 'matchingRuleUse' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -489,7 +489,7 @@ class DITContentRule(SchemaElement): NAMEs or OIDs of attributes which may not be present in an entry of the object class. (tuple of strings) """ - schema_attribute = u'dITContentRules' + schema_attribute = 'dITContentRules' token_defaults = { 'NAME':(()), 'DESC':(None,), @@ -547,7 +547,7 @@ class DITStructureRule(SchemaElement): NAMEs or OIDs of allowed structural object classes of superior entries in the DIT (tuple of strings) """ - schema_attribute = u'dITStructureRules' + schema_attribute = 'dITStructureRules' token_defaults = { 'NAME':(()), @@ -610,7 +610,7 @@ class NameForm(SchemaElement): NAMEs or OIDs of additional attributes an RDN may contain (tuple of strings) """ - schema_attribute = u'nameForms' + schema_attribute = 'nameForms' token_defaults = { 'NAME':(()), 'DESC':(None,), diff --git a/Lib/ldap/schema/subentry.py b/Lib/ldap/schema/subentry.py index 86e996f0..b83d819b 100644 --- a/Lib/ldap/schema/subentry.py +++ b/Lib/ldap/schema/subentry.py @@ -191,7 +191,7 @@ def tree(self,schema_element_class,schema_element_filters=None): # This helps with falsely assigned OIDs. continue assert se_obj.__class__==schema_element_class, \ - "Schema element referenced by %s must be of class %s but was %s" % ( + "Schema element referenced by {} must be of class {} but was {}".format( se_oid,schema_element_class.__name__,se_obj.__class__ ) for s in se_obj.sup or ('_',): @@ -216,7 +216,7 @@ def getoid(self,se_class,nameoroid,raise_keyerror=0): result_oid = self.name2oid[se_class][nameoroid_stripped] except KeyError: if raise_keyerror: - raise KeyError('No registered %s-OID for nameoroid %s' % (se_class.__name__,repr(nameoroid_stripped))) + raise KeyError('No registered {}-OID for nameoroid {}'.format(se_class.__name__,repr(nameoroid_stripped))) else: result_oid = nameoroid_stripped return result_oid @@ -249,7 +249,7 @@ def get_obj(self,se_class,nameoroid,default=None,raise_keyerror=0): se_obj = self.sed[se_class][se_oid] except KeyError: if raise_keyerror: - raise KeyError('No ldap.schema.%s instance with nameoroid %s and se_oid %s' % ( + raise KeyError('No ldap.schema.{} instance with nameoroid {} and se_oid {}'.format( se_class.__name__,repr(nameoroid),repr(se_oid)) ) else: @@ -461,7 +461,7 @@ def urlfetch(uri,trace_level=0): l=ldap.initialize(ldap_url.initializeUrl(),trace_level) l.protocol_version = ldap.VERSION3 - l.simple_bind_s(ldap_url.who or u'', ldap_url.cred or u'') + l.simple_bind_s(ldap_url.who or '', ldap_url.cred or '') subschemasubentry_dn = l.search_subschemasubentry_s(ldap_url.dn) if subschemasubentry_dn is None: s_temp = None diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index f6ac2d1a..1708b468 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ ldap.syncrepl - for implementing syncrepl consumer (see RFC 4533) diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 0e03fcc2..cf3b6a3f 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -62,7 +62,7 @@ def ldapUrlEscape(s): """Returns URL encoding of string s""" return quote(s).replace(',','%2C').replace('/','%2F') -class LDAPUrlExtension(object): +class LDAPUrlExtension: """ Class for parsing and unparsing LDAP URL extensions as described in RFC 4516. @@ -103,9 +103,9 @@ def _parse(self,extension): def unparse(self): if self.exvalue is None: - return '%s%s' % ('!'*(self.critical>0),self.extype) + return '{}{}'.format('!'*(self.critical>0),self.extype) else: - return '%s%s=%s' % ( + return '{}{}={}'.format( '!'*(self.critical>0), self.extype,quote(self.exvalue or '') ) @@ -114,7 +114,7 @@ def __str__(self): return self.unparse() def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -176,7 +176,7 @@ def __str__(self): return ','.join(str(v) for v in self.values()) def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -198,7 +198,7 @@ def unparse(self): return ','.join(v.unparse() for v in self.values()) -class LDAPUrl(object): +class LDAPUrl: """ Class for parsing and unparsing LDAP URLs as described in RFC 4516. @@ -340,7 +340,7 @@ def initializeUrl(self): hostport = ldapUrlEscape(self.hostport) else: hostport = self.hostport - return '%s://%s' % (self.urlscheme,hostport) + return f'{self.urlscheme}://{hostport}' def unparse(self): """ @@ -361,7 +361,7 @@ def unparse(self): hostport = ldapUrlEscape(self.hostport) else: hostport = self.hostport - ldap_url = '%s://%s/%s?%s?%s?%s' % ( + ldap_url = '{}://{}/{}?{}?{}?{}'.format( self.urlscheme, hostport,dn,attrs_str,scope_str,filterstr ) @@ -395,7 +395,7 @@ def htmlHREF(self,urlPrefix='',hrefText=None,hrefTarget=None): raise TypeError("hrefTarget must be str, not " + type(hrefTarget).__name__) target = ' target="%s"' % hrefTarget - return '%s' % ( + return '{}'.format( target, urlPrefix, self.unparse(), hrefText ) @@ -403,7 +403,7 @@ def __str__(self): return self.unparse() def __repr__(self): - return '<%s.%s instance at %s: %s>' % ( + return '<{}.{} instance at {}: {}>'.format( self.__class__.__module__, self.__class__.__name__, hex(id(self)), @@ -420,7 +420,7 @@ def __getattr__(self,name): else: return None else: - raise AttributeError('%s has no attribute %s' % ( + raise AttributeError('{} has no attribute {}'.format( self.__class__.__name__,name )) return result # __getattr__() diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 1371bef2..b57cd44a 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ slapdtest - module for spawning test instances of OpenLDAP's slapd server diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 141f459b..8dcb7133 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ slapdtest - module for spawning test instances of OpenLDAP's slapd server @@ -159,7 +158,7 @@ def combined_logger( return new_logger # end of combined_logger() -class SlapdObject(object): +class SlapdObject: """ Controller class for a slapd instance, OpenLDAP's server. diff --git a/Tests/__init__.py b/Tests/__init__.py index d6545732..ea28d0ce 100644 --- a/Tests/__init__.py +++ b/Tests/__init__.py @@ -1,11 +1,9 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap See https://www.python-ldap.org/ for details. """ -from __future__ import absolute_import from . import t_bind from . import t_cext diff --git a/Tests/t_cext.py b/Tests/t_cext.py index c271531a..8333b0f4 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's C wrapper module _ldap @@ -28,7 +27,7 @@ class TestLdapCExtension(SlapdTestCase): @classmethod def setUpClass(cls): - super(TestLdapCExtension, cls).setUpClass() + super().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( @@ -52,14 +51,14 @@ def setUpClass(cls): ) def setUp(self): - super(TestLdapCExtension, self).setUp() + super().setUp() self._writesuffix = None def tearDown(self): # cleanup test subtree if self._writesuffix is not None: self.server.ldapdelete(self._writesuffix, recursive=True) - super(TestLdapCExtension, self).tearDown() + super().tearDown() @property def writesuffix(self): @@ -288,7 +287,7 @@ def test_anon_rootdse_search(self): '', _ldap.SCOPE_BASE, '(objectClass=*)', - [str('objectClass'), str('namingContexts')], + ['objectClass', 'namingContexts'], ) self.assertEqual(type(m), type(0)) result, pmsg, msgid, ctrls = l.result4(m, _ldap.MSG_ALL, self.timeout) diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index 6878617e..aa9a77e6 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.cidict diff --git a/Tests/t_edit.py b/Tests/t_edit.py index f79ff18f..5d8b3f06 100644 --- a/Tests/t_edit.py +++ b/Tests/t_edit.py @@ -13,7 +13,7 @@ class EditionTests(SlapdTestCase): @classmethod def setUpClass(cls): - super(EditionTests, cls).setUpClass() + super().setUpClass() base = cls.server.suffix suffix_dc = base.split(',')[0][3:] diff --git a/Tests/t_ldap_dn.py b/Tests/t_ldap_dn.py index d62ec719..86d36403 100644 --- a/Tests/t_ldap_dn.py +++ b/Tests/t_ldap_dn.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.dn diff --git a/Tests/t_ldap_filter.py b/Tests/t_ldap_filter.py index da96446c..313b3733 100644 --- a/Tests/t_ldap_filter.py +++ b/Tests/t_ldap_filter.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.filter diff --git a/Tests/t_ldap_functions.py b/Tests/t_ldap_functions.py index 45931dab..5434a37a 100644 --- a/Tests/t_ldap_functions.py +++ b/Tests/t_ldap_functions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.functions diff --git a/Tests/t_ldap_modlist.py b/Tests/t_ldap_modlist.py index 53c80cc3..62596247 100644 --- a/Tests/t_ldap_modlist.py +++ b/Tests/t_ldap_modlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.modlist @@ -44,7 +43,7 @@ def test_addModlist(self): result_modlist.sort() self.assertEqual( test_modlist, result_modlist, - 'addModlist(%s) returns\n%s\ninstead of\n%s.' % ( + 'addModlist({}) returns\n{}\ninstead of\n{}.'.format( repr(entry),repr(result_modlist),repr(test_modlist) ) ) @@ -146,7 +145,7 @@ def test_modifyModlist(self): self.assertEqual( test_modlist, result_modlist, - 'modifyModlist(%s,%s) returns\n%s\ninstead of\n%s.' % ( + 'modifyModlist({},{}) returns\n{}\ninstead of\n{}.'.format( repr(old_entry), repr(new_entry), repr(result_modlist), diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 684fdf7b..89f21a43 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -28,7 +28,7 @@ ] -class BaseTestOptions(object): +class BaseTestOptions: """Common tests for getting/setting options Used in subclasses below @@ -97,7 +97,7 @@ def _test_controls(self, option): # data must be bytes or None self.set_option( option, - [TEST_CTRL[0][0], TEST_CTRL[0][1], u'data'] + [TEST_CTRL[0][0], TEST_CTRL[0][1], 'data'] ) def test_client_controls(self): diff --git a/Tests/t_ldap_sasl.py b/Tests/t_ldap_sasl.py index 9cf675af..40ab27e9 100644 --- a/Tests/t_ldap_sasl.py +++ b/Tests/t_ldap_sasl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.sasl @@ -47,7 +46,7 @@ class TestSasl(SlapdTestCase): @classmethod def setUpClass(cls): - super(TestSasl, cls).setUpClass() + super().setUpClass() ldif = LDIF.format( suffix=cls.server.suffix, rootdn=cls.server.root_dn, @@ -72,7 +71,7 @@ def test_external_ldapi(self): ldap_conn.sasl_interactive_bind_s("", auth) self.assertEqual( ldap_conn.whoami_s().lower(), - "dn:{}".format(self.server.root_dn.lower()) + f"dn:{self.server.root_dn.lower()}" ) @requires_tls() @@ -89,7 +88,7 @@ def test_external_tlscert(self): ldap_conn.sasl_interactive_bind_s("", auth) self.assertEqual( ldap_conn.whoami_s().lower(), - "dn:{}".format(self.certsubject) + f"dn:{self.certsubject}" ) if __name__ == '__main__': diff --git a/Tests/t_ldap_schema_subentry.py b/Tests/t_ldap_schema_subentry.py index e05c957a..60a584d1 100644 --- a/Tests/t_ldap_schema_subentry.py +++ b/Tests/t_ldap_schema_subentry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's class ldap.schema.SubSchema diff --git a/Tests/t_ldap_schema_tokenizer.py b/Tests/t_ldap_schema_tokenizer.py index 0890379a..20e86ab6 100644 --- a/Tests/t_ldap_schema_tokenizer.py +++ b/Tests/t_ldap_schema_tokenizer.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.schema.tokenizer diff --git a/Tests/t_ldap_syncrepl.py b/Tests/t_ldap_syncrepl.py index 51104148..6acc82c4 100644 --- a/Tests/t_ldap_syncrepl.py +++ b/Tests/t_ldap_syncrepl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.syncrepl @@ -266,7 +265,7 @@ def syncrepl_present(self, uuids, refreshDeletes=False): pass -class BaseSyncreplTests(object): +class BaseSyncreplTests: """ 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, @@ -279,7 +278,7 @@ class BaseSyncreplTests(object): @classmethod def setUpClass(cls): - super(BaseSyncreplTests, cls).setUpClass() + super().setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -292,13 +291,13 @@ def setUpClass(cls): ) def setUp(self): - super(BaseSyncreplTests, self).setUp() + super().setUp() self.tester = None self.suffix = None def tearDown(self): self.tester.unbind_s() - super(BaseSyncreplTests, self).tearDown() + super().tearDown() def create_client(self): raise NotImplementedError @@ -433,7 +432,7 @@ def test_refreshAndPersist_cancelled(self): class TestSyncrepl(BaseSyncreplTests, SlapdTestCase): def setUp(self): - super(TestSyncrepl, self).setUp() + super().setUp() self.tester = SyncreplClient( self.server.ldap_uri, self.server.root_dn, diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 6d44ff1d..e54bbfd4 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldap.ldapobject @@ -91,7 +90,7 @@ class Test00_SimpleLDAPObject(SlapdTestCase): @classmethod def setUpClass(cls): - super(Test00_SimpleLDAPObject, cls).setUpClass() + super().setUpClass() # insert some Foo* objects via ldapadd cls.server.ldapadd( LDIF_TEMPLATE % { @@ -297,11 +296,11 @@ def test_search_subschema(self): self.assertEqual( sorted(subschema), [ - u'attributeTypes', - u'ldapSyntaxes', - u'matchingRuleUse', - u'matchingRules', - u'objectClasses' + 'attributeTypes', + 'ldapSyntaxes', + 'matchingRuleUse', + 'matchingRules', + 'objectClasses' ] ) @@ -318,7 +317,7 @@ def test004_enotconn(self): info = ldap_err.args[0]['info'] expected_info = os.strerror(errno.ENOTCONN) if info != expected_info: - self.fail("expected info=%r, got %r" % (expected_info, info)) + self.fail(f"expected info={expected_info!r}, got {info!r}") else: self.fail("expected SERVER_DOWN, got %r" % r) @@ -360,10 +359,10 @@ def assertIsSubclass(self, cls, other): def test_simple_bind_noarg(self): l = self.ldap_object_class(self.server.ldap_uri) l.simple_bind_s() - self.assertEqual(l.whoami_s(), u'') + self.assertEqual(l.whoami_s(), '') l = self.ldap_object_class(self.server.ldap_uri) l.simple_bind_s(None, None) - self.assertEqual(l.whoami_s(), u'') + self.assertEqual(l.whoami_s(), '') def _check_byteswarning(self, warning, expected_message): self.assertIs(warning.category, ldap.LDAPBytesWarning) @@ -401,16 +400,16 @@ def test_multiple_starttls(self): def test_dse(self): dse = self._ldap_conn.read_rootdse_s() self.assertIsInstance(dse, dict) - self.assertEqual(dse[u'supportedLDAPVersion'], [b'3']) + self.assertEqual(dse['supportedLDAPVersion'], [b'3']) keys = set(dse) # SASL info may be missing in restricted build environments - keys.discard(u'supportedSASLMechanisms') + keys.discard('supportedSASLMechanisms') self.assertEqual( keys, - {u'configContext', u'entryDN', u'namingContexts', u'objectClass', - u'structuralObjectClass', u'subschemaSubentry', - u'supportedControl', u'supportedExtension', u'supportedFeatures', - u'supportedLDAPVersion'} + {'configContext', 'entryDN', 'namingContexts', 'objectClass', + 'structuralObjectClass', 'subschemaSubentry', + 'supportedControl', 'supportedExtension', 'supportedFeatures', + 'supportedLDAPVersion'} ) self.assertEqual( self._ldap_conn.get_naming_contexts(), @@ -542,20 +541,20 @@ def test103_reconnect_get_state(self): self.assertEqual( l1.__getstate__(), { - str('_last_bind'): ( + '_last_bind': ( 'simple_bind_s', (bind_dn, 'user1_pw'), {} ), - str('_options'): [(17, 3)], - str('_reconnects_done'): 0, - str('_retry_delay'): 60.0, - str('_retry_max'): 1, - str('_start_tls'): 0, - str('_trace_level'): ldap._trace_level, - str('_trace_stack_limit'): 5, - str('_uri'): self.server.ldap_uri, - str('timeout'): -1, + '_options': [(17, 3)], + '_reconnects_done': 0, + '_retry_delay': 60.0, + '_retry_max': 1, + '_start_tls': 0, + '_trace_level': ldap._trace_level, + '_trace_stack_limit': 5, + '_uri': self.server.ldap_uri, + 'timeout': -1, }, ) @@ -595,14 +594,14 @@ def _open_ldap_conn(self, who=None, cred=None, **kwargs): self._sock = socket.create_connection( (self.server.hostname, self.server.port) ) - return super(Test03_SimpleLDAPObjectWithFileno, self)._open_ldap_conn( + return super()._open_ldap_conn( who=who, cred=cred, fileno=self._sock.fileno(), **kwargs ) def tearDown(self): self._sock.close() del self._sock - super(Test03_SimpleLDAPObjectWithFileno, self).tearDown() + super().tearDown() def reset_connection(self): self._sock.close() diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 398dc892..4e7744b4 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldapurl @@ -160,7 +159,7 @@ def test_ldapurl(self): ldap_url_obj = LDAPUrl(ldapUrl=ldap_url_str) self.assertEqual( ldap_url_obj, test_ldap_url_obj, - 'Attributes of LDAPUrl(%s) are:\n%s\ninstead of:\n%s' % ( + 'Attributes of LDAPUrl({}) are:\n{}\ninstead of:\n{}'.format( repr(ldap_url_str), repr(ldap_url_obj), repr(test_ldap_url_obj), @@ -170,7 +169,7 @@ def test_ldapurl(self): unparsed_ldap_url_obj = LDAPUrl(ldapUrl=unparsed_ldap_url_str) self.assertEqual( unparsed_ldap_url_obj, test_ldap_url_obj, - 'Attributes of LDAPUrl(%s) are:\n%s\ninstead of:\n%s' % ( + 'Attributes of LDAPUrl({}) are:\n{}\ninstead of:\n{}'.format( repr(unparsed_ldap_url_str), repr(unparsed_ldap_url_obj), repr(test_ldap_url_obj), diff --git a/Tests/t_ldif.py b/Tests/t_ldif.py index 254e68d6..2f9ed679 100644 --- a/Tests/t_ldif.py +++ b/Tests/t_ldif.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Automatic tests for python-ldap's module ldif From 2647f59dfd25b4bed1cc69ead0b9dce7a59dcf4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 13 Nov 2020 22:19:26 +0100 Subject: [PATCH 03/73] Removed some python2-only code from SlapdObject https://github.com/python-ldap/python-ldap/pull/391 --- Lib/slapdtest/_slapdtest.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Lib/slapdtest/_slapdtest.py b/Lib/slapdtest/_slapdtest.py index 8dcb7133..36841110 100644 --- a/Lib/slapdtest/_slapdtest.py +++ b/Lib/slapdtest/_slapdtest.py @@ -461,15 +461,7 @@ def stop(self): self._proc.terminate() self.wait() self._cleanup_rundir() - if hasattr(atexit, 'unregister'): - # Python 3 - atexit.unregister(self.stop) - elif hasattr(atexit, '_exithandlers'): - # Python 2, can be None during process shutdown - try: - atexit._exithandlers.remove(self.stop) - except ValueError: - pass + atexit.unregister(self.stop) def restart(self): """ From c2e045de99aa5dd3e626c0dcad62282436a61015 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Fri, 6 Nov 2020 17:14:37 +0100 Subject: [PATCH 04/73] Add missing cidict.copy() method The copy() method vanished when cidict was re-implemented on top of MutableMapping abstract base class. See: commit 2a2cef38feb7f259ea6b5735a6ce3e46812e217d Fixes: https://github.com/python-ldap/python-ldap/issues/387 Signed-off-by: Christian Heimes --- Lib/ldap/cidict.py | 8 ++++++++ Tests/t_cidict.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index a5ac29e0..ac19bd7d 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -53,6 +53,14 @@ def clear(self): self._keys.clear() self._data.clear() + def copy(self): + inst = self.__class__.__new__(self.__class__) + inst._data = self._data.copy() + inst._keys = self._keys.copy() + return inst + + __copy__ = copy + # Backwards compatibility def has_key(self, key): diff --git a/Tests/t_cidict.py b/Tests/t_cidict.py index aa9a77e6..97146ec8 100644 --- a/Tests/t_cidict.py +++ b/Tests/t_cidict.py @@ -71,6 +71,19 @@ def test_cidict_data(self): assert data == {'a': 1, 'b': 2} self.assertEqual(len(w), 1) + def test_copy(self): + cix1 = ldap.cidict.cidict( + {"a": 1, "B": 2} + ) + cix2 = cix1.copy() + self.assertEqual(cix1, cix2) + cix1["c"] = 3 + self.assertNotIn("c", cix2) + cix2["C"] = 4 + self.assertNotEqual(cix1, cix2) + self.assertEqual(list(cix1.keys()), ["a", "B", "c"]) + self.assertEqual(list(cix2.keys()), ["a", "B", "C"]) + if __name__ == '__main__': unittest.main() From a66dcc36aa28b120bb5341f934be9bf8de7e21d7 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Nov 2020 08:08:23 -0800 Subject: [PATCH 05/73] Tidy up Travis configuration - Use the Python 3.9 release rather than '*-dev' - Drop 'sudo' configuration as it is deprecated - Drop 'dist: xenial' configuration, it is now the default https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration --- .travis.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17d46bf3..95cc1489 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: python group: travis_latest -sudo: false - cache: pip addons: @@ -35,22 +33,16 @@ matrix: - TOXENV=py37 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - python: 3.8 env: - TOXENV=py38 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - - python: 3.9-dev + - python: 3.9 env: - TOXENV=py39 - CFLAGS_std="-std=c99" - WITH_GCOV=1 - dist: xenial - sudo: true - python: 3.6 env: - TOXENV=py3-nosasltls From 0ef617548176a89cbb7fa2ee7f748920e10fb5bf Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 14 Nov 2020 13:05:35 +0100 Subject: [PATCH 06/73] LDAPUrl now treats urlscheme cases insensitive https://github.com/python-ldap/python-ldap/pull/347 Fixes: https://github.com/python-ldap/python-ldap/issues/280 Signed-off-by: Christian Heimes Co-authored-by: David Mulder --- Doc/reference/ldapurl.rst | 5 +++++ Lib/ldapurl.py | 15 ++++----------- Tests/t_ldapurl.py | 30 ++++++++++++++++++++++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Doc/reference/ldapurl.rst b/Doc/reference/ldapurl.rst index de86de7e..eb2106b3 100644 --- a/Doc/reference/ldapurl.rst +++ b/Doc/reference/ldapurl.rst @@ -65,6 +65,11 @@ A :py:class:`LDAPUrl` object represents a complete LDAP URL. .. autoclass:: ldapurl.LDAPUrl :members: + .. versionchanged:: 3.4.0 + + The urlscheme is now case insensitive and always converted to lower + case. ``LDAP://localhost`` is equivalent to ``ldap://localhost``. + LDAP URL extensions ^^^^^^^^^^^^^^^^^^^ diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index cf3b6a3f..820f3d84 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -48,14 +48,9 @@ def isLDAPUrl(s): + """Returns True if s is a LDAP URL, else False """ - Returns 1 if s is a LDAP URL, 0 else - """ - s_lower = s.lower() - return \ - s_lower.startswith('ldap://') or \ - s_lower.startswith('ldaps://') or \ - s_lower.startswith('ldapi://') + return s.lower().startswith(('ldap://', 'ldaps://', 'ldapi://')) def ldapUrlEscape(s): @@ -235,7 +230,7 @@ def __init__( extensions=None, who=None,cred=None ): - self.urlscheme=urlscheme + self.urlscheme=urlscheme.lower() self.hostport=hostport self.dn=dn self.attrs=attrs @@ -270,9 +265,7 @@ def _parse(self,ldap_url): if not isLDAPUrl(ldap_url): raise ValueError('Value %s for ldap_url does not seem to be a LDAP URL.' % (repr(ldap_url))) scheme,rest = ldap_url.split('://',1) - self.urlscheme = scheme.strip() - if not self.urlscheme in ['ldap','ldaps','ldapi']: - raise ValueError('LDAP URL contains unsupported URL scheme %s.' % (self.urlscheme)) + self.urlscheme = scheme.lower() slash_pos = rest.find('/') qemark_pos = rest.find('?') if (slash_pos==-1) and (qemark_pos==-1): diff --git a/Tests/t_ldapurl.py b/Tests/t_ldapurl.py index 4e7744b4..f9c72098 100644 --- a/Tests/t_ldapurl.py +++ b/Tests/t_ldapurl.py @@ -33,18 +33,23 @@ class TestIsLDAPUrl(unittest.TestCase): 'ldap://host.com:6666/o=University%20of%20Michigan,':1, 'ldap://ldap.itd.umich.edu/c=GB?objectClass?one':1, 'ldap://ldap.question.com/o=Question%3f,c=US?mail':1, - 'ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04)':1, + 'ldap://ldap.netscape.com/o=Babsco,c=US???(int=%5c00%5c00%5c00%5c04)':1, 'ldap:///??sub??bindname=cn=Manager%2co=Foo':1, 'ldap:///??sub??!bindname=cn=Manager%2co=Foo':1, # More examples from various sources 'ldap://ldap.nameflow.net:1389/c%3dDE':1, 'ldap://root.openldap.org/dc=openldap,dc=org':1, - 'ldap://root.openldap.org/dc=openldap,dc=org':1, + 'ldaps://root.openldap.org/dc=openldap,dc=org':1, 'ldap://x500.mh.se/o=Mitthogskolan,c=se????1.2.752.58.10.2=T.61':1, 'ldp://root.openldap.org/dc=openldap,dc=org':0, 'ldap://localhost:1389/ou%3DUnstructured%20testing%20tree%2Cdc%3Dstroeder%2Cdc%3Dcom??one':1, 'ldaps://ldap.example.com/c%3dDE':1, 'ldapi:///dc=stroeder,dc=de????x-saslmech=EXTERNAL':1, + 'LDAP://localhost': True, + 'LDAPS://localhost': True, + 'LDAPI://%2Frun%2Fldap.sock': True, + ' ldap://space.example': False, + 'ldap ://space.example': False, } def test_isLDAPUrl(self): @@ -56,6 +61,11 @@ def test_isLDAPUrl(self): ldap_url, result, expected, ) ) + if expected: + LDAPUrl(ldapUrl=ldap_url) + else: + with self.assertRaises(ValueError): + LDAPUrl(ldapUrl=ldap_url) class TestParseLDAPUrl(unittest.TestCase): @@ -144,6 +154,22 @@ class TestParseLDAPUrl(unittest.TestCase): dn='dc=stroeder,dc=com', ), ), + ( + 'LDAPS://localhost:12345/dc=stroeder,dc=com', + LDAPUrl( + urlscheme='ldaps', + hostport='localhost:12345', + dn='dc=stroeder,dc=com', + ), + ), + ( + 'ldaps://localhost:12345/dc=stroeder,dc=com', + LDAPUrl( + urlscheme='LDAPS', + hostport='localhost:12345', + dn='dc=stroeder,dc=com', + ), + ), ( 'ldapi://%2ftmp%2fopenldap2-1389/dc=stroeder,dc=com', LDAPUrl( From 4993d4ade24faceb3321f85f5e69a8b83c4f77f4 Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 4 Dec 2020 15:09:00 +0100 Subject: [PATCH 07/73] Do not decode attribute values of post read control Fixes: https://github.com/python-ldap/python-ldap/issues/399 https://github.com/python-ldap/python-ldap/pull/400 --- Doc/reference/ldap-controls.rst | 3 +++ Lib/ldap/controls/readentry.py | 2 +- Tests/t_ldap_controls_readentry.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Tests/t_ldap_controls_readentry.py diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 1520b241..999eaa9a 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -197,6 +197,9 @@ search. :rfc:`4527` - Lightweight Directory Access Protocol (LDAP): Read Entry Controls +.. versionchanged:: 4.0 + The attribute values of the entry now consists of `bytes` instead of ISO8859-1 decoded `str`. + .. autoclass:: ldap.controls.readentry.ReadEntryControl :members: diff --git a/Lib/ldap/controls/readentry.py b/Lib/ldap/controls/readentry.py index c5db4247..7b2a7e89 100644 --- a/Lib/ldap/controls/readentry.py +++ b/Lib/ldap/controls/readentry.py @@ -42,7 +42,7 @@ def decodeControlValue(self,encodedControlValue): self.dn = str(decodedEntry[0]) self.entry = {} for attr in decodedEntry[1]: - self.entry[str(attr[0])] = [ str(attr_value) for attr_value in attr[1] ] + self.entry[str(attr[0])] = [ bytes(attr_value) for attr_value in attr[1] ] class PreReadControl(ReadEntryControl): diff --git a/Tests/t_ldap_controls_readentry.py b/Tests/t_ldap_controls_readentry.py new file mode 100644 index 00000000..313a7905 --- /dev/null +++ b/Tests/t_ldap_controls_readentry.py @@ -0,0 +1,28 @@ +import os +import unittest + +# Switch off processing .ldaprc or ldap.conf before importing _ldap +os.environ['LDAPNOINIT'] = '1' + +from ldap.controls import readentry # noqa: E402 + + +PRC_ENC = b'db\x04)uid=Administrator,cn=users,l=school,l=dev0503\x04\tentryUUID1&\x04$5d96cc2c-8e13-103a-8ca5-2f74868e0e44' +PRC_DEC = b'0\x0b\x04\tentryUUID' + + +class TestLibldapControls(unittest.TestCase): + + def test_pagedresults_encode(self): + pr = readentry.PostReadControl(True, ['entryUUID']) + self.assertEqual(pr.encodeControlValue(), PRC_DEC) + + def test_readentry_decode(self): + pr = readentry.PostReadControl(True, ['entryUUID']) + pr.decodeControlValue(PRC_ENC) + self.assertIsInstance(pr.dn, str) + self.assertEqual(pr.entry, {'entryUUID': [b'5d96cc2c-8e13-103a-8ca5-2f74868e0e44']}) + + +if __name__ == '__main__': + unittest.main() From 7dfa21a1631adb3a7c7c142f7c5d5a152f9403b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Fri, 4 Dec 2020 15:20:14 +0100 Subject: [PATCH 08/73] Improve ppolicy documentation https://github.com/python-ldap/python-ldap/pull/397 --- Doc/conf.py | 1 + Doc/reference/ldap-controls.rst | 13 +++++++++++++ Lib/ldap/controls/ppolicy.py | 20 +++++++++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index 0fa396b8..30f27306 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -33,6 +33,7 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', ] try: diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 999eaa9a..37d7c1bc 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -209,3 +209,16 @@ search. .. autoclass:: ldap.controls.readentry.PostReadControl :members: + + +:py:mod:`ldap.controls.ppolicy` Password Policy Control +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. seealso:: + `draft-behera-ldap-password-policy `_ + +.. py:module:: ldap.controls.ppolicy + :synopsis: passworld policies + +.. autoclass:: ldap.controls.ppolicy.PasswordPolicyControl + :members: diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index ebc456ad..da7586f0 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -62,17 +62,31 @@ class PasswordPolicyResponseValue(univ.Sequence): class PasswordPolicyControl(ValueLessRequestControl,ResponseControl): + """ + Indicates the errors and warnings about the password policy. + + Attributes + ---------- + + timeBeforeExpiration : int + The time before the password expires. + + graceAuthNsRemaining : int + The number of grace authentications remaining. + + error: int + The password and authentication errors. + """ controlType = '1.3.6.1.4.1.42.2.27.8.5.1' def __init__(self,criticality=False): self.criticality = criticality - - def decodeControlValue(self,encodedControlValue): - ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) self.timeBeforeExpiration = None self.graceAuthNsRemaining = None self.error = None + def decodeControlValue(self,encodedControlValue): + ppolicyValue,_ = decoder.decode(encodedControlValue,asn1Spec=PasswordPolicyResponseValue()) warning = ppolicyValue.getComponentByName('warning') if warning.hasValue(): if 'timeBeforeExpiration' in warning: From 2fb97af1e025a95d1ea86b9aeac6235358e52278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89loi=20Rivard?= Date: Tue, 15 Dec 2020 11:09:32 +0100 Subject: [PATCH 09/73] Remove the outdated documentation copyright year https://github.com/python-ldap/python-ldap/pull/398 --- Doc/conf.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/conf.py b/Doc/conf.py index 30f27306..b883736e 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -10,6 +10,7 @@ # All configuration values have a default value; values that are commented out # serve to show the default value. +import datetime import sys import os @@ -54,7 +55,7 @@ # General substitutions. project = 'python-ldap' -copyright = '2008-2017, python-ldap project team' +copyright = 'python-ldap project team' # The default replacements for |version| and |release|, also used in various # other places throughout the built documents. From 25d0ca01b26ec213da3670819b5d0df48ec0f1d2 Mon Sep 17 00:00:00 2001 From: Fred Thomsen Date: Tue, 29 Jun 2021 06:35:53 -0400 Subject: [PATCH 10/73] Add note to FAQ regarding search referrals Several posts across the web in regards to this issue, so an additional note in the answer to this question seems warranted. Basing this answer on message this message in the mailing list: https://mail.python.org/pipermail/python-ldap/2014q1/003350.html https://github.com/python-ldap/python-ldap/pull/409 --- Doc/faq.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Doc/faq.rst b/Doc/faq.rst index 2152873b..492b95f2 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -65,6 +65,10 @@ connection.”* Alternatively, a Samba 4 AD returns the diagnostic message l = ldap.initialize('ldap://foobar') l.set_option(ldap.OPT_REFERRALS,0) + Note that setting the above option does NOT prevent search continuations + from being returned, rather only that ``libldap`` won't attempt to resolve + referrals. + **Q**: Why am I seeing a ``ldap.SUCCESS`` traceback as output? **A**: Most likely, you are using one of the non-synchronous calls, and probably From 7d6979073bf12538c2d1d9b218de3f47bbae8a2f Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 29 Jun 2021 15:15:24 +0200 Subject: [PATCH 11/73] Test with GHA Drop Travis CI and use Github Actions Signed-off-by: Christian Heimes --- .github/workflows/ci.yml | 35 +++++++++++++++++++ .travis.yml | 75 ---------------------------------------- tox.ini | 8 +++++ 3 files changed, 43 insertions(+), 75 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..0bc2ae0e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +--- +name: CI + +on: [push, pull_request] + +jobs: + distros: + name: "Ubuntu with Python ${{ matrix.python-version }}" + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + steps: + - name: Checkout + uses: "actions/checkout@v2" + - name: Install apt dependencies + run: | + set -ex + sudo apt update + sudo apt install -y ldap-utils slapd enchant libldap2-dev libsasl2-dev apparmor-utils + - name: Disable AppArmor + run: sudo aa-disable /usr/sbin/slapd + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: "Install Python dependencies" + run: | + set -xe + python -V + python -m pip install --upgrade pip setuptools + python -m pip install --upgrade tox tox-gh-actions + - name: "Test tox with Python ${{ matrix.python-version }}" + run: "python -m tox" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 95cc1489..00000000 --- a/.travis.yml +++ /dev/null @@ -1,75 +0,0 @@ -language: python -group: travis_latest - -cache: pip - -addons: - apt: - packages: - - ldap-utils - - slapd - - enchant - -# Note: when updating Python versions, also change setup.py and tox.ini -matrix: - include: - - os: osx - osx_image: xcode11.4 - language: minimal - env: - - TOXENV=macos - - CFLAGS_warnings="-Wall -Werror=declaration-after-statement" - - CFLAGS_std="-std=c99" - - python: 3.6 - env: - - TOXENV=py36 - - WITH_GCOV=1 - - python: pypy3 - env: - - TOXENV=pypy3 - - CFLAGS_std="-std=c99" - - python: 3.7 - env: - - TOXENV=py37 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.8 - env: - - TOXENV=py38 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.9 - env: - - TOXENV=py39 - - CFLAGS_std="-std=c99" - - WITH_GCOV=1 - - python: 3.6 - env: - - TOXENV=py3-nosasltls - - WITH_GCOV=1 - - python: 3.6 - env: - - TOXENV=py3-trace - - python: 3.6 - env: TOXENV=doc - allow_failures: - - env: - - TOXENV=pypy3 - -env: - global: - # -Wno-int-in-bool-context: don't complain about PyMem_MALLOC() - # -Werror: turn all warnings into fatal errors - # -Werror=declaration-after-statement: strict ISO C90 - - CFLAGS_warnings="-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement" - # Keep C90 compatibility where possible. - # (Python 3.8+ headers use C99 features, so this needs to be overridable.) - - CFLAGS_std="-std=c90" - # pass CFLAGS, CI (for Travis CI) and WITH_GCOV to tox tasks - - TOX_TESTENV_PASSENV="CFLAGS CI WITH_GCOV" - -install: - - python3 -m pip install "pip>=7.1.0" - - python3 -m pip install tox-travis tox codecov - -script: CFLAGS="$CFLAGS_warnings $CFLAGS_std" python3 -m tox diff --git a/tox.ini b/tox.ini index 65773a2c..26982d0f 100644 --- a/tox.ini +++ b/tox.ini @@ -8,6 +8,14 @@ envlist = py36,py37,py38,py39,py3-nosasltls,doc,py3-trace minver = 1.8 +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38, doc, py3-nosasltls + 3.9: py39, py3-trace + pypy3: pypy3 + [testenv] deps = passenv = WITH_GCOV From 9f9bc4ca3fd21c66dfaa83378992fa7f24e452e9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Jul 2021 15:40:15 +0200 Subject: [PATCH 12/73] [RELICENCE] Adopt the MIT licence for future contributions to python-ldap * Adopt the MIT licence for future contributions - Add LICENCE.MIT for all contributions from July 2021 onward - Explain the licencing situation - Start a list of people who agree to relicence their past contributions to MIT https://github.com/python-ldap/python-ldap/pull/417 --- LICENCE.MIT | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ README | 24 ++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 LICENCE.MIT diff --git a/LICENCE.MIT b/LICENCE.MIT new file mode 100644 index 00000000..c78bcb9b --- /dev/null +++ b/LICENCE.MIT @@ -0,0 +1,51 @@ +The MIT License applies to contributions committed after July 1st, 2021, and +to all contributions by the following authors: + +* ​A. Karl Kornel +* Alex Willmer +* Aymeric Augustin +* Bernhard M. Wiedemann +* Bradley Baetz +* Éloi Rivard +* Eyal Cherevatzki +* Fred Thomsen +* Ivan A. Melnikov +* johnthagen +* Jonathon Reinhart +* Jon Dufresne +* Martin Basti +* Marti Raudsepp +* Miro Hrončok +* Petr Viktorin +* Pieterjan De Potter +* Raphaël Barrois +* Robert Kuska +* Stanislav Láznička +* Tobias Bräutigam +* Tom van Dijk +* William Brown + + +------------------------------------------------------------------------------- + +MIT License + +Copyright (c) 2021 python-ldap contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README b/README index 81db9bb3..8045555d 100644 --- a/README +++ b/README @@ -127,3 +127,27 @@ their contributions and input into this package. Thanks! We may have missed someone: please mail us if we have omitted your name. + +Licence +======= + +The python-ldap project comes with a LICENCE file. + +We are aware that its text is unclear, but it cannot be changed: +all authors of python-ldap would need to approve the licence change, +but a complete list of all the authors is not available. +(Note that the Git repository of the project is incomplete. +Furthermore, commits imported from CVS lack authorship information; users +"stroeder" or "leonard" are commiters (reviewers), but sometimes not +authors of the committed code.) + +The current maintainers assume that the license is the sentence that refers +to "Python-style license" and assume this means a highly permissive open source +license that only requires preservation of the text of the LICENCE file +(including the disclaimer paragraph). + +------------------------------------------------------------------------------- + +All contributions committed since July 1st, 2021, as well as some past +contributions, are licensed under the MIT license. +The MIT licence and more details are listed in the file LICENCE.MIT. From 69867f5817bc81194fdb45fc478f7ec2b36f27cf Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Thu, 1 Jul 2021 15:42:05 +0200 Subject: [PATCH 13/73] Clean up tox environments & run tox on Fedora in CI * Add CFLAGS and -std=c90 runs to tox config * Add Python 3.10 to tox config * List the pypy3 tox environment * Remove mentions of Python 2 from docs & Makefile * Update Travis CI mention in setup.py * Add tox-fedora GH workflow https://github.com/python-ldap/python-ldap/pull/419 --- .github/workflows/tox-fedora.yml | 34 ++++++++++++++++++++++++++++++++ Doc/installing.rst | 10 +++++----- Doc/sample_workflow.rst | 6 +++--- Makefile | 2 +- setup.py | 2 +- tox.ini | 11 +++++++++-- 6 files changed, 53 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/tox-fedora.yml diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml new file mode 100644 index 00000000..c0dbb45c --- /dev/null +++ b/.github/workflows/tox-fedora.yml @@ -0,0 +1,34 @@ +on: [push, pull_request] + +name: Tox on Fedora + +jobs: + tox_test: + name: Tox env "${{matrix.tox_env}}" on Fedora + steps: + - uses: actions/checkout@v2 + - name: Run Tox tests + uses: fedora-python/tox-github-action@master + with: + tox_env: ${{ matrix.tox_env }} + dnf_install: > + @c-development openldap-devel python3-devel + openldap-servers openldap-clients lcov clang-analyzer valgrind + enchant + strategy: + matrix: + tox_env: + - py36 + - py37 + - py38 + - py39 + - py310 + - c90-py36 + - c90-py37 + - py3-nosasltls + - py3-trace + - pypy3 + - doc + + # Use GitHub's Linux Docker host + runs-on: ubuntu-latest diff --git a/Doc/installing.rst b/Doc/installing.rst index 56778220..521910e4 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -130,7 +130,7 @@ Alpine Packages for building:: - # apk add build-base openldap-dev python2-dev python3-dev + # apk add build-base openldap-dev python3-dev CentOS ------ @@ -145,12 +145,12 @@ Debian Packages for building and testing:: - # apt-get install build-essential python3-dev python2.7-dev \ + # apt-get install build-essential python3-dev \ libldap2-dev libsasl2-dev slapd ldap-utils tox \ lcov valgrind - + .. note:: - + On older releases ``tox`` was called ``python-tox``. Fedora @@ -159,7 +159,7 @@ Fedora Packages for building and testing:: # dnf install "@C Development Tools and Libraries" openldap-devel \ - python2-devel python3-devel python3-tox \ + python3-devel python3-tox \ lcov clang-analyzer valgrind .. note:: diff --git a/Doc/sample_workflow.rst b/Doc/sample_workflow.rst index 60d60cac..76017034 100644 --- a/Doc/sample_workflow.rst +++ b/Doc/sample_workflow.rst @@ -61,15 +61,15 @@ This will run tests on all supported versions of Python that you have installed, skipping the ones you don't. To run a subset of test environments, run for example:: - (__venv__)$ tox -e py27,py36 + (__venv__)$ tox -e py36,py39 In addition to ``pyXY`` environments, we have extra environments for checking things independent of the Python version: * ``doc`` checks syntax and spelling of the documentation * ``coverage-report`` generates a test coverage report for Python code. - It must be used last, e.g. ``tox -e py27,py36,coverage-report``. -* ``py2-nosasltls`` and ``py3-nosasltls`` check functionality without + It must be used last, e.g. ``tox -e py36,py39,coverage-report``. +* ``py3-nosasltls`` check functionality without SASL and TLS bindings compiled in. diff --git a/Makefile b/Makefile index f7360a66..577ba883 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ lcov-clean: if [ -d build ]; then find build -name '*.gc??' -delete; fi lcov-coverage: - WITH_GCOV=1 tox -e py27,py36 + WITH_GCOV=1 tox -e py36 $(LCOV_INFO): build lcov --capture --directory build --output-file $(LCOV_INFO) diff --git a/setup.py b/setup.py index 20c31c5f..119b5715 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', - # Note: when updating Python versions, also change .travis.yml and tox.ini + # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', 'Topic :: Internet', diff --git a/tox.ini b/tox.ini index 26982d0f..aaef8b5a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,8 +4,8 @@ # and then run "tox" from this directory. [tox] -# Note: when updating Python versions, also change setup.py and .travis.yml -envlist = py36,py37,py38,py39,py3-nosasltls,doc,py3-trace +# Note: when updating Python versions, also change setup.py and .github/worlflows/* +envlist = py{36,37,38,39,310},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 minver = 1.8 [gh-actions] @@ -21,6 +21,8 @@ deps = passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement -std=c99 commands = {envpython} -bb -Werror \ -m unittest discover -v -s Tests -p 't_*' {posargs} @@ -51,6 +53,11 @@ setenv = PYTHON_LDAP_TRACE_FILE={envtmpdir}/trace.log commands = {[testenv]commands} +[testenv:c90] +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -Werror=declaration-after-statement -std=c90 +commands = {envpython} -Werror -c "import ldap" # we just test compilation here + [testenv:macos] # Travis CI macOS image does not have slapd # SDK libldap does not support ldap_init_fd From 2fc51b2c13a76913b439b826e674213b97d08f49 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Tue, 3 Aug 2021 10:22:11 +0200 Subject: [PATCH 14/73] Add OPT_X_TLS_REQUIRE_SAN Add bindings for OPT_X_TLS_REQUIRE_SAN option. The flag was introduced in OpenLDAP 2.4.52 to configure subject alternative name verification. Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 23 +++++++++++++++++++++++ Lib/ldap/constants.py | 3 +++ Modules/constants_generated.h | 4 ++++ Modules/options.c | 6 ++++++ Tests/t_cext.py | 23 +++++++++++++++++++++++ 5 files changed, 59 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 16220f3b..57485c7a 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -346,6 +346,29 @@ TLS options :py:const:`OPT_X_TLS_HARD` Same as :py:const:`OPT_X_TLS_DEMAND` +.. py:data:: OPT_X_TLS_REQUIRE_SAN + + get/set how OpenLDAP validates subject alternative name extension, + available in OpenSSL 2.4.52 and newer. + + :py:const:`OPT_X_TLS_NEVER` + Don't check SAN + + :py:const:`OPT_X_TLS_ALLOW` + Check SAN first, always fall back to subject common name (default) + + :py:const:`OPT_X_TLS_TRY` + Check SAN first, only fall back to subject common name, when no SAN + extension is present (:rfc:`6125` conform validation) + + :py:const:`OPT_X_TLS_DEMAND` + Validate peer cert chain and host name + + :py:const:`OPT_X_TLS_HARD` + Require SAN, don't fall back to subject common name + + .. versionadded:: 3.4.0 + .. py:data:: OPT_X_TLS_ALLOW Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index b6ec0d33..1c1d76a7 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -298,6 +298,9 @@ class Str(Constant): TLSInt('OPT_X_TLS_PROTOCOL_MIN', optional=True), TLSInt('OPT_X_TLS_PACKAGE', optional=True), + # Added in OpenLDAP 2.4.52 + TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), + Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), Int('OPT_X_SASL_AUTHCID'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 4a4cdb3e..e357fa23 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -249,6 +249,10 @@ add_int(OPT_X_TLS_PROTOCOL_MIN); add_int(OPT_X_TLS_PACKAGE); #endif +#if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) +add_int(OPT_X_TLS_REQUIRE_SAN); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index 549a6726..5ef0b86e 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -88,6 +88,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_REQUIRE_SAN + case LDAP_OPT_X_TLS_REQUIRE_SAN: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: @@ -298,6 +301,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_REQUIRE_SAN + case LDAP_OPT_X_TLS_REQUIRE_SAN: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 8333b0f4..816af8dd 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -932,6 +932,29 @@ def test_tls_packages(self): package = _ldap.get_option(_ldap.OPT_X_TLS_PACKAGE) self.assertIn(package, {"GnuTLS", "MozNSS", "OpenSSL"}) + @unittest.skipUnless( + hasattr(_ldap, "OPT_X_TLS_REQUIRE_SAN"), + reason="Test requires OPT_X_TLS_REQUIRE_SAN" + ) + def test_require_san(self): + l = self._open_conn(bind=False) + value = l.get_option(_ldap.OPT_X_TLS_REQUIRE_SAN) + self.assertIn( + value, + { + _ldap.OPT_X_TLS_NEVER, + _ldap.OPT_X_TLS_ALLOW, + _ldap.OPT_X_TLS_TRY, + _ldap.OPT_X_TLS_DEMAND, + _ldap.OPT_X_TLS_HARD, + } + ) + l.set_option(_ldap.OPT_X_TLS_REQUIRE_SAN, _ldap.OPT_X_TLS_TRY) + self.assertEqual( + l.get_option(_ldap.OPT_X_TLS_REQUIRE_SAN), + _ldap.OPT_X_TLS_TRY + ) + if __name__ == '__main__': unittest.main() From 84bbf5e59559ebbec22aef3ba2c12ffbf327e36b Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 28 Jun 2021 11:03:02 +0200 Subject: [PATCH 15/73] Fix SASL get/set options on big endian platforms The options OPT_X_SASL_SSF_MIN, OPT_X_SASL_SSF_MAX, and OPT_X_SASL_SSF take *ber_len_t as input and output arguments. ber_len_t is defined as unsigned long: ``` /* LBER lengths (32 bits or larger) */ #define LBER_LEN_T long typedef unsigned LBER_LEN_T ber_len_t; ``` Wrong type handling is causing issues on big endian platforms. Signed-off-by: Christian Heimes --- LICENCE.MIT | 1 + Modules/options.c | 41 ++++++++++++++++++++++++++++++----------- Tests/t_ldapobject.py | 23 ++++++++++++++++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/LICENCE.MIT b/LICENCE.MIT index c78bcb9b..d4882df3 100644 --- a/LICENCE.MIT +++ b/LICENCE.MIT @@ -6,6 +6,7 @@ to all contributions by the following authors: * Aymeric Augustin * Bernhard M. Wiedemann * Bradley Baetz +* Christian Heimes * Éloi Rivard * Eyal Cherevatzki * Fred Thomsen diff --git a/Modules/options.c b/Modules/options.c index 5ef0b86e..d4d20724 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -43,6 +43,10 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) double doubleval; char *strval; struct timeval tv; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif void *ptr; LDAP *ld; LDAPControl **controls = NULL; @@ -92,10 +96,6 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif #endif -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF_MIN: - case LDAP_OPT_X_SASL_SSF_MAX: -#endif #ifdef LDAP_OPT_X_KEEPALIVE_IDLE case LDAP_OPT_X_KEEPALIVE_IDLE: #endif @@ -111,6 +111,16 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 0; ptr = &intval; break; + +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF_MIN: + case LDAP_OPT_X_SASL_SSF_MAX: + if (!PyArg_Parse(value, "k:set_option", &blen)) + return 0; + ptr = &blen; + break; +#endif + case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE @@ -138,6 +148,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) return 0; ptr = strval; break; + case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Float valued timeval options */ @@ -242,6 +253,10 @@ LDAP_get_option(LDAPObject *self, int option) LDAPAPIInfo apiinfo; LDAPControl **lcs; char *strval; +#if HAVE_SASL + /* unsigned long */ + ber_len_t blen; +#endif PyObject *extensions, *v; Py_ssize_t i, num_extensions; @@ -280,9 +295,6 @@ LDAP_get_option(LDAPObject *self, int option) return v; -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF: -#endif case LDAP_OPT_REFERRALS: case LDAP_OPT_RESTART: case LDAP_OPT_DEREF: @@ -305,10 +317,6 @@ LDAP_get_option(LDAPObject *self, int option) case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif #endif -#ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SSF_MIN: - case LDAP_OPT_X_SASL_SSF_MAX: -#endif #ifdef LDAP_OPT_X_SASL_NOCANON case LDAP_OPT_X_SASL_NOCANON: #endif @@ -330,6 +338,17 @@ LDAP_get_option(LDAPObject *self, int option) return option_error(res, "ldap_get_option"); return PyInt_FromLong(intval); +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF: + case LDAP_OPT_X_SASL_SSF_MIN: + case LDAP_OPT_X_SASL_SSF_MAX: + /* ber_len_t options (unsigned long)*/ + res = LDAP_int_get_option(self, option, &blen); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyLong_FromUnsignedLong(blen); +#endif + case LDAP_OPT_HOST_NAME: case LDAP_OPT_URI: #ifdef LDAP_OPT_DEFBASE diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index e54bbfd4..0a089c91 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -334,7 +334,7 @@ def test005_invalid_credentials(self): @requires_sasl() @requires_ldapi() - def test006_sasl_extenal_bind_s(self): + def test006_sasl_external_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()) @@ -343,6 +343,27 @@ def test006_sasl_extenal_bind_s(self): l.sasl_external_bind_s(authz_id=authz_id) self.assertEqual(l.whoami_s(), authz_id.lower()) + @requires_sasl() + @requires_ldapi() + def test006_sasl_options(self): + l = self.ldap_object_class(self.server.ldapi_uri) + + minssf = l.get_option(ldap.OPT_X_SASL_SSF_MIN) + self.assertGreaterEqual(minssf, 0) + self.assertLessEqual(minssf, 256) + maxssf = l.get_option(ldap.OPT_X_SASL_SSF_MAX) + self.assertGreaterEqual(maxssf, 0) + # libldap sets SSF_MAX to INT_MAX + self.assertLessEqual(maxssf, 2**31 - 1) + + l.set_option(ldap.OPT_X_SASL_SSF_MIN, 56) + l.set_option(ldap.OPT_X_SASL_SSF_MAX, 256) + self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MIN), 56) + self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MAX), 256) + + l.sasl_external_bind_s() + self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn.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=*)') From d9ded15a8c69c5891bdb6bc89d71a3ea4d3674a9 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 16 Sep 2021 14:07:57 +0200 Subject: [PATCH 16/73] Handle unknown LDAP result code Prevent ``SystemError: error return without exception set`` when LDAP server returns an unknown LDAP result code. Fixes: https://github.com/python-ldap/python-ldap/issues/240 Signed-off-by: Christian Heimes --- Modules/constants.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Modules/constants.c b/Modules/constants.c index 8b902e02..07d60653 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -31,7 +31,8 @@ static PyObject *errobjects[LDAP_ERROR_MAX - LDAP_ERROR_MIN + 1]; PyObject * LDAPerr(int errnum) { - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) { + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { PyErr_SetNone(errobjects[errnum + LDAP_ERROR_OFFSET]); } else { @@ -88,10 +89,13 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) ldap_get_option(l, LDAP_OPT_ERROR_STRING, &error); } - if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX) + if (errnum >= LDAP_ERROR_MIN && errnum <= LDAP_ERROR_MAX && + errobjects[errnum + LDAP_ERROR_OFFSET] != NULL) { errobj = errobjects[errnum + LDAP_ERROR_OFFSET]; - else + } + else { errobj = LDAPexception_class; + } info = PyDict_New(); if (info == NULL) { From f1c702ebdc231ca6f5ca255f38698ccd8c2743f5 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 15 Sep 2021 11:22:43 +0200 Subject: [PATCH 17/73] Implement OPT_X_SASL_SSF_EXTERNAL setter The option flag ``OPT_X_SASL_SSF_EXTERNAL`` never worked because the set_option code didn't handle the flag correctly. Fixes: https://github.com/python-ldap/python-ldap/issues/423 Signed-off-by: Christian Heimes --- Modules/options.c | 7 +++++++ Tests/t_ldapobject.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/Modules/options.c b/Modules/options.c index d4d20724..db5fde3e 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -115,6 +115,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: + case LDAP_OPT_X_SASL_SSF_EXTERNAL: if (!PyArg_Parse(value, "k:set_option", &blen)) return 0; ptr = &blen; @@ -261,6 +262,12 @@ LDAP_get_option(LDAPObject *self, int option) Py_ssize_t i, num_extensions; switch (option) { +#ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SSF_EXTERNAL: + /* Write-only options */ + PyErr_SetString(PyExc_ValueError, "write-only option"); + return NULL; +#endif case LDAP_OPT_API_INFO: apiinfo.ldapai_info_version = LDAP_API_INFO_VERSION; res = LDAP_int_get_option(self, option, &apiinfo); diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 0a089c91..3bcc00a2 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -362,6 +362,9 @@ def test006_sasl_options(self): self.assertEqual(l.get_option(ldap.OPT_X_SASL_SSF_MAX), 256) l.sasl_external_bind_s() + with self.assertRaisesRegex(ValueError, "write-only option"): + l.get_option(ldap.OPT_X_SASL_SSF_EXTERNAL) + l.set_option(ldap.OPT_X_SASL_SSF_EXTERNAL, 256) self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn.lower()) def test007_timeout(self): From 478f0b4886ede9d58ff02e499fe85a809fefb518 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 12:17:17 +0200 Subject: [PATCH 18/73] Add another batch of names to LICENCE.MIT (#430) --- LICENCE.MIT | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LICENCE.MIT b/LICENCE.MIT index d4882df3..0c2021f6 100644 --- a/LICENCE.MIT +++ b/LICENCE.MIT @@ -9,6 +9,7 @@ to all contributions by the following authors: * Christian Heimes * Éloi Rivard * Eyal Cherevatzki +* Florian Best * Fred Thomsen * Ivan A. Melnikov * johnthagen @@ -17,6 +18,7 @@ to all contributions by the following authors: * Martin Basti * Marti Raudsepp * Miro Hrončok +* Paul Aurich * Petr Viktorin * Pieterjan De Potter * Raphaël Barrois @@ -24,6 +26,7 @@ to all contributions by the following authors: * Stanislav Láznička * Tobias Bräutigam * Tom van Dijk +* Wentao Han * William Brown From 7206ef8d51c00c32dd34adc1cd967a84ba2d47d1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:39:58 +0200 Subject: [PATCH 19/73] Bump version to 3.4.0 --- Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 2d88dc07..ef958a13 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.3.0' +__version__ = '3.4.0' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 820f3d84..cce4e806 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 0afebd84..7e69a594 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index b57cd44a..bb59e7fa 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.3.0' +__version__ = '3.4.0' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 4901d051e72bd899b5bdba22c160083c9c4516cc Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:42:37 +0200 Subject: [PATCH 20/73] Bump removal of deprecated functions to next release This should be done at the *start* of a release cycle. --- Lib/ldap/cidict.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/cidict.py b/Lib/ldap/cidict.py index ac19bd7d..f846fd29 100644 --- a/Lib/ldap/cidict.py +++ b/Lib/ldap/cidict.py @@ -85,7 +85,7 @@ def strlist_minus(a,b): a,b are supposed to be lists of case-insensitive strings. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) @@ -105,7 +105,7 @@ def strlist_intersection(a,b): Return intersection of two lists of case-insensitive strings a,b. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) @@ -125,7 +125,7 @@ def strlist_union(a,b): Return union of two lists of case-insensitive strings a,b. """ warnings.warn( - "strlist functions are deprecated and will be removed in 3.4", + "strlist functions are deprecated and will be removed in 3.5", category=DeprecationWarning, stacklevel=2, ) From 6516eeb3b77c5e8f9ccaacc75617b38eb0640ab6 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 17 Sep 2021 15:37:26 +0200 Subject: [PATCH 21/73] Add a CHANGELOG --- CHANGES | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/CHANGES b/CHANGES index 711b665e..78f596de 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,48 @@ +Released 3.4.0 2021-09-17 + +This release requires Python 3.6 or above, +and is tested with Python 3.6 to 3.10. +Python 2 is no longer supported. + +New code in the python-ldap project is available under the MIT licence +(available in ``LICENCE.MIT`` in the source). Several contributors have agreed +to apply this licence their previous contributions as well. +See the ``README`` for details. + +The following undocumented functions are deprecated and scheduled for removal: +- ``ldap.cidict.strlist_intersection`` +- ``ldap.cidict.strlist_minus`` +- ``ldap.cidict.strlist_union`` + +Changes: +* On MacOS, remove option to make LDAP connections from a file descriptor + when built with the system libldap (which lacks the underlying function, + ``ldap_init_fd``) +* Attribute values of the post read control are now ``bytes`` + instead of ISO8859-1 decoded ``str`` +* ``LDAPUrl`` now treats urlscheme as case-insensitive +* Several OpenLDAP options are now supported: + * ``OPT_X_TLS_REQUIRE_SAN`` + * ``OPT_X_SASL_SSF_EXTERNAL`` + * ``OPT_X_TLS_PEERCERT`` + +Fixes: +* The ``copy()`` method of ``cidict`` was added back. It was unintentionally + removed in 3.3.0 +* Fixed getting/setting ``SASL`` options on big endian platforms +* Unknown LDAP result code are now converted to ``LDAPexception``, + rather than raising a ``SystemError``. + +slapdtest: +* Show stderr of slapd -Ttest +* ``SlapdObject`` uses directory-based configuration of ``slapd`` +* ``SlapdObject`` startup is now faster + +Infrastructure: +* CI now runs on GitHub Actions rather than Travis CI. + + +---------------------------------------------------------------- Released 3.3.0 2020-06-18 Highlights: From 4ee61f4e2f81ae7c2e7293bda576630ed5ad3142 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 15 Nov 2021 10:38:03 +0100 Subject: [PATCH 22/73] Adjust version note in documentation --- Doc/installing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 521910e4..e4518c11 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -111,7 +111,7 @@ Build prerequisites The following software packages are required to be installed on the local system when building python-ldap: -- `Python`_ version 2.7, or 3.4 or later including its development files +- `Python`_ including its development files - C compiler corresponding to your Python version (on Linux, it is usually ``gcc``) - `OpenLDAP`_ client libs version 2.4.11 or later; it is not possible and not supported to build with prior versions. From 404c36b702c5b3a7e60729745c8bda16098b1472 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 15:39:04 +0100 Subject: [PATCH 23/73] Set today's release date --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 78f596de..92d9d414 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Released 3.4.0 2021-09-17 +Released 3.4.0 2021-11-26 This release requires Python 3.6 or above, and is tested with Python 3.6 to 3.10. From faa011b41f7141121546045925d809d54e70f5fd Mon Sep 17 00:00:00 2001 From: Kevin Backhouse Date: Fri, 15 Oct 2021 15:21:37 +0100 Subject: [PATCH 24/73] Fix ReDoS in regex. --- Lib/ldap/schema/tokenizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/ldap/schema/tokenizer.py b/Lib/ldap/schema/tokenizer.py index 69823f2b..623b86d5 100644 --- a/Lib/ldap/schema/tokenizer.py +++ b/Lib/ldap/schema/tokenizer.py @@ -13,7 +13,7 @@ r"|" # or r"([^'$()\s]+)" # string of length >= 1 without '$() or whitespace r"|" # or - r"('(?:[^'\\]|\\\\|\\.)*?'(?!\w))" + r"('(?:[^'\\]|\\.)*'(?!\w))" # any string or empty string surrounded by unescaped # single quotes except if right quote is succeeded by # alphanumeric char From bf4c165d1e86846c7204ccb5a42a3facf3f1e4c1 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 15:42:22 +0100 Subject: [PATCH 25/73] Add release note --- CHANGES | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGES b/CHANGES index 92d9d414..c358fa9e 100644 --- a/CHANGES +++ b/CHANGES @@ -14,6 +14,11 @@ The following undocumented functions are deprecated and scheduled for removal: - ``ldap.cidict.strlist_minus`` - ``ldap.cidict.strlist_union`` +Security fixes: +* Fix inefficient regular expression which allows denial-of-service attacks + when parsing specially-crafted LDAP schema. + (GHSL-2021-117) + Changes: * On MacOS, remove option to make LDAP connections from a file descriptor when built with the system libldap (which lacks the underlying function, From 47975ee75c84748abfb8bb7456d5cc0a34cfce41 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Fri, 26 Nov 2021 16:09:26 +0100 Subject: [PATCH 26/73] Fix inefficient regular expression in the schema tokenizer Fixes: GHSL-2021-117 Thank you to GitHub team members @erik-krogh (Erik Krogh Kristensen) and @yoff (Rasmus Petersen) for discovering the issue. https://github.com/python-ldap/python-ldap/pull/444 Co-authored-by: Kevin Backhouse From 9d7ca92108e8e116f45243f1833c87c9556ea893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 18 Jan 2022 14:34:32 +0000 Subject: [PATCH 27/73] Update BooleanControl encoding/decoding to use pyasn1 --- Lib/ldap/controls/simple.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/controls/simple.py b/Lib/ldap/controls/simple.py index 05f6760d..96837e2a 100644 --- a/Lib/ldap/controls/simple.py +++ b/Lib/ldap/controls/simple.py @@ -7,6 +7,9 @@ import struct,ldap from ldap.controls import RequestControl,ResponseControl,LDAPControl,KNOWN_RESPONSE_CONTROLS +from pyasn1.type import univ +from pyasn1.codec.ber import encoder,decoder + class ValueLessRequestControl(RequestControl): """ @@ -57,8 +60,6 @@ class BooleanControl(LDAPControl): booleanValue Boolean (True/False or 1/0) which is the boolean controlValue. """ - boolean2ber = { 1:'\x01\x01\xFF', 0:'\x01\x01\x00' } - ber2boolean = { '\x01\x01\xFF':1, '\x01\x01\x00':0 } def __init__(self,controlType=None,criticality=False,booleanValue=False): self.controlType = controlType @@ -66,10 +67,11 @@ def __init__(self,controlType=None,criticality=False,booleanValue=False): self.booleanValue = booleanValue def encodeControlValue(self): - return self.boolean2ber[int(self.booleanValue)] + return encoder.encode(self.booleanValue,asn1Spec=univ.Boolean()) def decodeControlValue(self,encodedControlValue): - self.booleanValue = self.ber2boolean[encodedControlValue] + decodedValue,_ = decoder.decode(encodedControlValue,asn1Spec=univ.Boolean()) + self.booleanValue = bool(int(decodedValue)) class ManageDSAITControl(ValueLessRequestControl): From 61cb47f0063c7e2a7c36affb6675690f2f90868d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 26 Jan 2022 11:02:25 +0000 Subject: [PATCH 28/73] Mention TLS gotcha in the FAQ --- Doc/faq.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/faq.rst b/Doc/faq.rst index 492b95f2..e96fc030 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -84,6 +84,11 @@ connection.”* Alternatively, a Samba 4 AD returns the diagnostic message `LDAPv2 is considered historic `_ since many years. +**Q**: My TLS settings are ignored/TLS isn't working? + + **A**: Make sure you call `set_option( ldap.OPT_X_TLS_NEWCTX, 0 )` + after changing any of the `OPT_X_TLS_*` options. + Installing From d7b65a737356a6677ee2d76b667a925334b6b699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 26 Jan 2022 12:13:45 +0000 Subject: [PATCH 29/73] Add TLS to wordlist --- Doc/spelling_wordlist.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index c24ab486..e6c2aedd 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -144,6 +144,7 @@ subtree syncrepl syntaxes timelimit +TLS tracebacks tuple tuples From e595f43c83e586d37542dd7dd8f497c57825021a Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Tue, 2 Nov 2021 22:08:42 +0100 Subject: [PATCH 30/73] Fix wrong asserts in test_test_flags Introduced in c5ad8025632fb5e5e84cced5a0f8c7ddda1e8dae --- Tests/t_cext.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_cext.py b/Tests/t_cext.py index 816af8dd..33fbf29a 100644 --- a/Tests/t_cext.py +++ b/Tests/t_cext.py @@ -234,11 +234,11 @@ def test_test_flags(self): if 'TLS' in disabled: self.assertFalse(_ldap.TLS_AVAIL) else: - self.assertFalse(_ldap.TLS_AVAIL) + self.assertTrue(_ldap.TLS_AVAIL) if 'SASL' in disabled: self.assertFalse(_ldap.SASL_AVAIL) else: - self.assertFalse(_ldap.SASL_AVAIL) + self.assertTrue(_ldap.SASL_AVAIL) def test_simple_bind(self): l = self._open_conn() From e75c24dd70dcf10c8315d6f30ecf98f2c30f08e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=A4rdeman?= Date: Mon, 24 Jan 2022 18:02:04 +0100 Subject: [PATCH 31/73] ldap.schema.models.Entry - Fix init() I'm guessing this is partly due to commit 2be2c6bb8f44792d506cb1759fe06ff4cda3c5cf And the update() method can't ever have worked? --- Lib/ldap/schema/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/ldap/schema/models.py b/Lib/ldap/schema/models.py index d73420c5..3d9322c0 100644 --- a/Lib/ldap/schema/models.py +++ b/Lib/ldap/schema/models.py @@ -7,7 +7,7 @@ import sys import ldap.cidict -from collections import UserDict as IterableUserDict +from collections import UserDict from ldap.schema.tokenizer import split_tokens,extract_tokens @@ -640,7 +640,7 @@ def __str__(self): return '( %s )' % ''.join(result) -class Entry(IterableUserDict): +class Entry(UserDict): """ Schema-aware implementation of an LDAP entry class. @@ -653,7 +653,7 @@ def __init__(self,schema,dn,entry): self._attrtype2keytuple = {} self._s = schema self.dn = dn - IterableUserDict.IterableUserDict.__init__(self,{}) + super().__init__() self.update(entry) def _at2key(self,nameoroid): @@ -674,7 +674,7 @@ def _at2key(self,nameoroid): return t def update(self,dict): - for key, value in dict.values(): + for key, value in dict.items(): self[key] = value def __contains__(self,nameoroid): From 1e20c2d540a201d035debdbfcd61ab96dc6bd8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 27 Jan 2022 10:35:56 +0000 Subject: [PATCH 32/73] Check whether libldap is threadsafe on startup. Closes #432 --- Lib/ldap/constants.py | 2 -- Modules/constants.c | 10 ++++++++++ setup.cfg | 6 ++++-- setup.py | 1 - 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 1c1d76a7..f76609b9 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -341,9 +341,7 @@ class Str(Constant): # XXX - these should be errors Int('URL_ERR_BADSCOPE'), Int('URL_ERR_MEM'), - # Int('LIBLDAP_R'), - Feature('LIBLDAP_R', 'HAVE_LIBLDAP_R'), Feature('SASL_AVAIL', 'HAVE_SASL'), Feature('TLS_AVAIL', 'HAVE_TLS'), Feature('INIT_FD_AVAIL', 'HAVE_LDAP_INIT_FD'), diff --git a/Modules/constants.c b/Modules/constants.c index 07d60653..8d6f63b0 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -197,6 +197,8 @@ int LDAPinit_constants(PyObject *m) { PyObject *exc, *nobj; + struct ldap_apifeature_info info = { 1, "X_OPENLDAP_THREAD_SAFE", 0 }; + int thread_safe = 0; /* simple constants */ @@ -221,6 +223,14 @@ LDAPinit_constants(PyObject *m) return -1; Py_INCREF(LDAPexception_class); +#ifdef LDAP_API_FEATURE_X_OPENLDAP_THREAD_SAFE + if (ldap_get_option(NULL, LDAP_OPT_API_FEATURE_INFO, &info) == LDAP_SUCCESS) { + thread_safe = (info.ldapaif_version == 1); + } +#endif + if (PyModule_AddIntConstant(m, "LIBLDAP_R", thread_safe) != 0) + return -1; + /* Generated constants -- see Lib/ldap/constants.py */ #define add_err(n) do { \ diff --git a/setup.cfg b/setup.cfg index 01d43a06..48f36197 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,9 +21,11 @@ defines = HAVE_SASL HAVE_TLS HAVE_LIBLDAP_R extra_compile_args = extra_objects = +# Uncomment this if your libldap is not thread-safe and you need libldap_r +# instead # Example for full-featured build: # Support for StartTLS/LDAPS, SASL bind and reentrant libldap_r. -libs = ldap_r lber +#libs = ldap_r lber # Installation options [install] @@ -33,7 +35,7 @@ optimize = 1 # Linux distributors/packagers should adjust these settings [bdist_rpm] provides = python-ldap -requires = python libldap-2_4 +requires = python libldap-2 vendor = python-ldap project packager = python-ldap team distribution_name = openSUSE 11.x diff --git a/setup.py b/setup.py index 119b5715..b1939571 100644 --- a/setup.py +++ b/setup.py @@ -132,7 +132,6 @@ class OpenLDAP2: extra_objects = LDAP_CLASS.extra_objects, runtime_library_dirs = (not sys.platform.startswith("win"))*LDAP_CLASS.library_dirs, define_macros = LDAP_CLASS.defines + \ - ('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)] + \ [ From 5f8fcfde7797095fe250d58b591edb66d83ad15f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 1 Feb 2022 12:02:53 +0000 Subject: [PATCH 33/73] Regenerate Modules/constants_generated.h --- Modules/constants_generated.h | 43 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index e357fa23..6070c31d 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -76,10 +76,12 @@ add_err(TOO_LATE); add_err(CANNOT_CANCEL); #endif + #if defined(LDAP_ASSERTION_FAILED) add_err(ASSERTION_FAILED); #endif + #if defined(LDAP_PROXIED_AUTHORIZATION_DENIED) add_err(PROXIED_AUTHORIZATION_DENIED); #endif @@ -192,6 +194,7 @@ add_int(OPT_URI); add_int(OPT_DEFBASE); #endif + #if HAVE_TLS #if defined(LDAP_OPT_X_TLS) @@ -217,18 +220,22 @@ add_int(OPT_X_TLS_TRY); add_int(OPT_X_TLS_VERSION); #endif + #if defined(LDAP_OPT_X_TLS_CIPHER) add_int(OPT_X_TLS_CIPHER); #endif + #if defined(LDAP_OPT_X_TLS_PEERCERT) add_int(OPT_X_TLS_PEERCERT); #endif + #if defined(LDAP_OPT_X_TLS_CRLCHECK) add_int(OPT_X_TLS_CRLCHECK); #endif + #if defined(LDAP_OPT_X_TLS_CRLFILE) add_int(OPT_X_TLS_CRLFILE); #endif @@ -241,14 +248,17 @@ add_int(OPT_X_TLS_CRL_ALL); add_int(OPT_X_TLS_NEWCTX); #endif + #if defined(LDAP_OPT_X_TLS_PROTOCOL_MIN) add_int(OPT_X_TLS_PROTOCOL_MIN); #endif + #if defined(LDAP_OPT_X_TLS_PACKAGE) add_int(OPT_X_TLS_PACKAGE); #endif + #if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) add_int(OPT_X_TLS_REQUIRE_SAN); #endif @@ -269,22 +279,27 @@ add_int(OPT_X_SASL_SSF_MAX); add_int(OPT_X_SASL_NOCANON); #endif + #if defined(LDAP_OPT_X_SASL_USERNAME) add_int(OPT_X_SASL_USERNAME); #endif + #if defined(LDAP_OPT_CONNECT_ASYNC) add_int(OPT_CONNECT_ASYNC); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_IDLE) add_int(OPT_X_KEEPALIVE_IDLE); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_PROBES) add_int(OPT_X_KEEPALIVE_PROBES); #endif + #if defined(LDAP_OPT_X_KEEPALIVE_INTERVAL) add_int(OPT_X_KEEPALIVE_INTERVAL); #endif @@ -309,36 +324,24 @@ add_int(OPT_SUCCESS); add_int(URL_ERR_BADSCOPE); add_int(URL_ERR_MEM); -#ifdef HAVE_LIBLDAP_R -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 1) != 0) - return -1; -#else -if (PyModule_AddIntConstant(m, "LIBLDAP_R", 0) != 0) - return -1; -#endif - #ifdef HAVE_SASL -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "SASL_AVAIL", 0) != 0) return -1; #endif + #ifdef HAVE_TLS -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "TLS_AVAIL", 0) != 0) return -1; #endif + #ifdef HAVE_LDAP_INIT_FD -if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 1) != 0) - return -1; +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 1) != 0) return -1; #else -if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 0) != 0) - return -1; +if (PyModule_AddIntConstant(m, "INIT_FD_AVAIL", 0) != 0) return -1; #endif add_string(CONTROL_MANAGEDSAIT); From e712033b0ff88463b6ca39d2b02f1779d4bbea16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 1 Feb 2022 12:03:11 +0000 Subject: [PATCH 34/73] Fix LDAP_VENDOR_VERSION check --- Modules/common.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/common.h b/Modules/common.h index 886024f2..bc554c85 100644 --- a/Modules/common.h +++ b/Modules/common.h @@ -16,7 +16,7 @@ #include #include -#if LDAP_API_VERSION < 2040 +#if LDAP_VENDOR_VERSION < 20400 #error Current python-ldap requires OpenLDAP 2.4.x #endif From 610b2ee59129a2650414370942dff6dec17d4226 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Thu, 16 Sep 2021 13:56:34 +0200 Subject: [PATCH 35/73] Implement support for OPT_X_TLS_PEERCERT Co-authored-by: Thomas Grainger Signed-off-by: Christian Heimes --- Doc/reference/ldap.rst | 7 ++++++- Lib/ldap/constants.py | 3 +++ Modules/berval.c | 2 +- Modules/constants_generated.h | 5 +++++ Modules/options.c | 18 ++++++++++++++++++ Tests/t_ldapobject.py | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 57485c7a..def77c66 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -406,7 +406,12 @@ TLS options .. py:data:: OPT_X_TLS_PEERCERT - Get peer's certificate as binary ASN.1 data structure (not supported) + Get peer's certificate as binary ASN.1 data structure (DER) + + .. versionadded:: 3.4.1 + + .. note:: + The option leaks memory with OpenLDAP < 2.5.8. .. py:data:: OPT_X_TLS_PROTOCOL_MIN diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index f76609b9..19bdd059 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -301,6 +301,9 @@ class Str(Constant): # Added in OpenLDAP 2.4.52 TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), + # Added in OpenLDAP 2.5 + TLSInt('OPT_X_TLS_PEERCERT', optional=True), + Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), Int('OPT_X_SASL_AUTHCID'), diff --git a/Modules/berval.c b/Modules/berval.c index 7435ee0a..6917baef 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -17,7 +17,7 @@ LDAPberval_to_object(const struct berval *bv) { PyObject *ret = NULL; - if (!bv) { + if (!bv || !bv->bv_val) { ret = Py_None; Py_INCREF(ret); } diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 6070c31d..ccb42782 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -263,6 +263,11 @@ add_int(OPT_X_TLS_PACKAGE); add_int(OPT_X_TLS_REQUIRE_SAN); #endif + +#if defined(LDAP_OPT_X_TLS_PEERCERT) +add_int(OPT_X_TLS_PEERCERT); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index db5fde3e..af775766 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -5,6 +5,7 @@ #include "LDAPObject.h" #include "ldapcontrol.h" #include "options.h" +#include "berval.h" void set_timeval_from_double(struct timeval *tv, double d) @@ -58,6 +59,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) case LDAP_OPT_API_FEATURE_INFO: #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: +#endif +#ifdef LDAP_OPT_X_TLS_PEERCERT + case LDAP_OPT_X_TLS_PEERCERT: #endif /* Read-only options */ PyErr_SetString(PyExc_ValueError, "read-only option"); @@ -254,6 +258,7 @@ LDAP_get_option(LDAPObject *self, int option) LDAPAPIInfo apiinfo; LDAPControl **lcs; char *strval; + struct berval berbytes; #if HAVE_SASL /* unsigned long */ ber_len_t blen; @@ -406,6 +411,19 @@ LDAP_get_option(LDAPObject *self, int option) ldap_memfree(strval); return v; +#ifdef HAVE_TLS +#ifdef LDAP_OPT_X_TLS_PEERCERT + case LDAP_OPT_X_TLS_PEERCERT: +#endif +#endif + /* Options dealing with raw data */ + res = LDAP_int_get_option(self, option, &berbytes); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + v = LDAPberval_to_object(&berbytes); + ldap_memfree(berbytes.bv_val); + return v; + case LDAP_OPT_TIMEOUT: case LDAP_OPT_NETWORK_TIMEOUT: /* Double-valued timeval options */ diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 3bcc00a2..07a78595 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -20,6 +20,11 @@ from slapdtest import requires_ldapi, requires_sasl, requires_tls from slapdtest import requires_init_fd +try: + from ssl import PEM_cert_to_DER_cert +except ImportError: + PEM_cert_to_DER_cert = None + LDIF_TEMPLATE = """dn: %(suffix)s objectClass: dcObject @@ -421,6 +426,36 @@ def test_multiple_starttls(self): l.simple_bind_s(self.server.root_dn, self.server.root_pw) self.assertEqual(l.whoami_s(), 'dn:' + self.server.root_dn) + @requires_tls() + @unittest.skipUnless( + hasattr(ldap, "OPT_X_TLS_PEERCERT"), + reason="Requires OPT_X_TLS_PEERCERT" + ) + def test_get_tls_peercert(self): + l = self.ldap_object_class(self.server.ldap_uri) + peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT) + self.assertEqual(peercert, None) + with self.assertRaises(ValueError): + l.set_option(ldap.OPT_X_TLS_PEERCERT, b"") + + l.set_option(ldap.OPT_X_TLS_CACERTFILE, self.server.cafile) + l.set_option(ldap.OPT_X_TLS_NEWCTX, 0) + l.start_tls_s() + + peercert = l.get_option(ldap.OPT_X_TLS_PEERCERT) + self.assertTrue(peercert) + self.assertIsInstance(peercert, bytes) + + if PEM_cert_to_DER_cert is not None: + with open(self.server.servercert) as f: + server_pem = f.read() + # remove text + begin = server_pem.find("-----BEGIN CERTIFICATE-----") + server_pem = server_pem[begin:-1] + + server_der = PEM_cert_to_DER_cert(server_pem) + self.assertEqual(server_der, peercert) + def test_dse(self): dse = self._ldap_conn.read_rootdse_s() self.assertIsInstance(dse, dict) From c4efbdabb35acab6be89435ed59608ee439153f9 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Sat, 18 Sep 2021 17:36:44 +0200 Subject: [PATCH 36/73] Use regex to locate PEM body --- Tests/t_ldapobject.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 07a78595..9e4e3311 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -3,9 +3,11 @@ See https://www.python-ldap.org/ for details. """ +import base64 import errno import linecache import os +import re import socket import unittest import pickle @@ -20,10 +22,10 @@ from slapdtest import requires_ldapi, requires_sasl, requires_tls from slapdtest import requires_init_fd -try: - from ssl import PEM_cert_to_DER_cert -except ImportError: - PEM_cert_to_DER_cert = None +PEM_CERT_RE = re.compile( + b'-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----', + re.DOTALL +) LDIF_TEMPLATE = """dn: %(suffix)s @@ -446,15 +448,12 @@ def test_get_tls_peercert(self): self.assertTrue(peercert) self.assertIsInstance(peercert, bytes) - if PEM_cert_to_DER_cert is not None: - with open(self.server.servercert) as f: - server_pem = f.read() - # remove text - begin = server_pem.find("-----BEGIN CERTIFICATE-----") - server_pem = server_pem[begin:-1] + with open(self.server.servercert, "rb") as f: + server_cert = f.read() + pem_body = PEM_CERT_RE.search(server_cert).group(1) + server_der = base64.b64decode(pem_body) - server_der = PEM_cert_to_DER_cert(server_pem) - self.assertEqual(server_der, peercert) + self.assertEqual(server_der, peercert) def test_dse(self): dse = self._ldap_conn.read_rootdse_s() From dbd7a3847121e24c3a32b10c6a9016129a08ea0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 14 Feb 2022 13:08:57 +0000 Subject: [PATCH 37/73] Process missing libldap options --- Lib/ldap/constants.py | 3 +++ Modules/constants_generated.h | 11 ++++++++++ Modules/options.c | 40 ++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 19bdd059..71994a8d 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -244,6 +244,7 @@ class Str(Constant): Int('OPT_SIZELIMIT'), Int('OPT_TIMELIMIT'), Int('OPT_REFERRALS', optional=True), + Int('OPT_RESULT_CODE'), Int('OPT_ERROR_NUMBER'), Int('OPT_RESTART'), Int('OPT_PROTOCOL_VERSION'), @@ -261,6 +262,7 @@ class Str(Constant): Int('OPT_TIMEOUT'), Int('OPT_REFHOPLIMIT'), Int('OPT_NETWORK_TIMEOUT'), + Int('OPT_TCP_USER_TIMEOUT', optional=True), Int('OPT_URI'), Int('OPT_DEFBASE', optional=True), @@ -299,6 +301,7 @@ class Str(Constant): TLSInt('OPT_X_TLS_PACKAGE', optional=True), # Added in OpenLDAP 2.4.52 + TLSInt('OPT_X_TLS_ECNAME', optional=True), TLSInt('OPT_X_TLS_REQUIRE_SAN', optional=True), # Added in OpenLDAP 2.5 diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index ccb42782..9df264ed 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -173,6 +173,7 @@ add_int(OPT_TIMELIMIT); add_int(OPT_REFERRALS); #endif +add_int(OPT_RESULT_CODE); add_int(OPT_ERROR_NUMBER); add_int(OPT_RESTART); add_int(OPT_PROTOCOL_VERSION); @@ -188,6 +189,11 @@ add_int(OPT_DEBUG_LEVEL); add_int(OPT_TIMEOUT); add_int(OPT_REFHOPLIMIT); add_int(OPT_NETWORK_TIMEOUT); + +#if defined(LDAP_OPT_TCP_USER_TIMEOUT) +add_int(OPT_TCP_USER_TIMEOUT); +#endif + add_int(OPT_URI); #if defined(LDAP_OPT_DEFBASE) @@ -259,6 +265,11 @@ add_int(OPT_X_TLS_PACKAGE); #endif +#if defined(LDAP_OPT_X_TLS_ECNAME) +add_int(OPT_X_TLS_ECNAME); +#endif + + #if defined(LDAP_OPT_X_TLS_REQUIRE_SAN) add_int(OPT_X_TLS_REQUIRE_SAN); #endif diff --git a/Modules/options.c b/Modules/options.c index af775766..ef9eddf2 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -41,6 +41,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) { int res; int intval; + unsigned int uintval; double doubleval; char *strval; struct timeval tv; @@ -57,6 +58,7 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) switch (option) { case LDAP_OPT_API_INFO: case LDAP_OPT_API_FEATURE_INFO: + case LDAP_OPT_DESC: #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: #endif @@ -116,10 +118,19 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) ptr = &intval; break; +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + case LDAP_OPT_TCP_USER_TIMEOUT: +#endif + if (!PyArg_Parse(value, "I:set_option", &uintval)) + return 0; + ptr = &uintval; + break; + #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: case LDAP_OPT_X_SASL_SSF_EXTERNAL: + case LDAP_OPT_X_SASL_MAXBUFSIZE: if (!PyArg_Parse(value, "k:set_option", &blen)) return 0; ptr = &blen; @@ -144,9 +155,15 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_CRLFILE case LDAP_OPT_X_TLS_CRLFILE: #endif +#ifdef LDAP_OPT_X_TLS_ECNAME + case LDAP_OPT_X_TLS_ECNAME: +#endif #endif #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SECPROPS: +#endif +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: #endif /* String valued options */ if (!PyArg_Parse(value, "s:set_option", &strval)) @@ -187,8 +204,8 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) } else { PyErr_Format(PyExc_ValueError, - "timeout must be >= 0 or -1/None for infinity, got %d", - option); + "timeout must be >= 0 or -1/None for infinity, got %f", + doubleval); return 0; } break; @@ -254,6 +271,7 @@ LDAP_get_option(LDAPObject *self, int option) { int res; int intval; + unsigned int uintval; struct timeval *tv; LDAPAPIInfo apiinfo; LDAPControl **lcs; @@ -268,6 +286,7 @@ LDAP_get_option(LDAPObject *self, int option) switch (option) { #ifdef HAVE_SASL + case LDAP_OPT_X_SASL_SECPROPS: case LDAP_OPT_X_SASL_SSF_EXTERNAL: /* Write-only options */ PyErr_SetString(PyExc_ValueError, "write-only option"); @@ -350,10 +369,20 @@ LDAP_get_option(LDAPObject *self, int option) return option_error(res, "ldap_get_option"); return PyInt_FromLong(intval); +#ifdef LDAP_OPT_TCP_USER_TIMEOUT + case LDAP_OPT_TCP_USER_TIMEOUT: +#endif + /* unsigned int options */ + res = LDAP_int_get_option(self, option, &uintval); + if (res != LDAP_OPT_SUCCESS) + return option_error(res, "ldap_get_option"); + return PyLong_FromUnsignedLong(uintval); + #ifdef HAVE_SASL case LDAP_OPT_X_SASL_SSF: case LDAP_OPT_X_SASL_SSF_MIN: case LDAP_OPT_X_SASL_SSF_MAX: + case LDAP_OPT_X_SASL_MAXBUFSIZE: /* ber_len_t options (unsigned long)*/ res = LDAP_int_get_option(self, option, &blen); if (res != LDAP_OPT_SUCCESS) @@ -388,9 +417,11 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PACKAGE case LDAP_OPT_X_TLS_PACKAGE: #endif +#ifdef LDAP_OPT_X_TLS_ECNAME + case LDAP_OPT_X_TLS_ECNAME: +#endif #endif #ifdef HAVE_SASL - case LDAP_OPT_X_SASL_SECPROPS: case LDAP_OPT_X_SASL_MECH: case LDAP_OPT_X_SASL_REALM: case LDAP_OPT_X_SASL_AUTHCID: @@ -398,6 +429,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_SASL_USERNAME case LDAP_OPT_X_SASL_USERNAME: #endif +#endif +#ifdef LDAP_OPT_SOCKET_BIND_ADDRESSES + case LDAP_OPT_SOCKET_BIND_ADDRESSES: #endif /* String-valued options */ res = LDAP_int_get_option(self, option, &strval); From 6462e58a68bbf674eeace5f6ac035b51966db7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Mon, 14 Feb 2022 15:08:08 +0000 Subject: [PATCH 38/73] In liblber {0, ""} corresponds to b'' and {0, NULL} corresponds to None --- Tests/t_ldap_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_ldap_options.py b/Tests/t_ldap_options.py index 89f21a43..e9bef591 100644 --- a/Tests/t_ldap_options.py +++ b/Tests/t_ldap_options.py @@ -23,8 +23,8 @@ ]) TEST_CTRL_EXPECTED = [ TEST_CTRL[0], - # get_option returns empty bytes - (TEST_CTRL[1][0], TEST_CTRL[1][1], b''), + # Noop has no value + (TEST_CTRL[1][0], TEST_CTRL[1][1], None), ] From 5cac85a7afeaf117d4b449e9e706a2679accc8eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 6 Apr 2022 11:42:43 +0100 Subject: [PATCH 39/73] Add TLS version numbers and remove unsupported TLS options Closes #67 --- Doc/reference/ldap.rst | 64 +++++++++++++++++++++++++---------- Lib/ldap/constants.py | 9 +++-- Modules/constants_generated.h | 36 ++++++++++++++++---- Modules/options.c | 6 ++++ 4 files changed, 90 insertions(+), 25 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index def77c66..9bd6b142 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -372,21 +372,27 @@ TLS options .. py:data:: OPT_X_TLS_ALLOW Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_DEMAND Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_HARD Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_NEVER Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + and :py:const:`OPT_X_TLS_REQUIRE_SAN` .. py:data:: OPT_X_TLS_TRY + Value for :py:const:`OPT_X_TLS_REQUIRE_CERT` + .. deprecated:: 3.3.0 This value is only used by slapd server internally. It will be removed in the future. @@ -400,10 +406,6 @@ TLS options get/set allowed cipher suites -.. py:data:: OPT_X_TLS_CTX - - get address of internal memory address of TLS context (**DO NOT USE**) - .. py:data:: OPT_X_TLS_PEERCERT Get peer's certificate as binary ASN.1 data structure (DER) @@ -417,8 +419,47 @@ TLS options get/set minimum protocol version (wire protocol version as int) - * ``0x303`` for TLS 1.2 - * ``0x304`` for TLS 1.3 +.. py:data:: OPT_X_TLS_PROTOCOL_MAX + + get/set maximum protocol version (wire protocol version as int), + available in OpenSSL 2.5 and newer. + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_SSL3 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents SSL 3 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_0 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.0 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_1 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.1 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_2 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.2 + + .. versionadded:: 3.4.1 + +.. py:data:: OPT_X_TLS_PROTOCOL_TLS1_3 + + Value for :py:const:`OPT_X_TLS_PROTOCOL_MIN` and + :py:const:`OPT_X_TLS_PROTOCOL_MAX`, represents TLS 1.3 + + .. versionadded:: 3.4.1 .. py:data:: OPT_X_TLS_VERSION @@ -428,12 +469,6 @@ TLS options get/set path to /dev/urandom (**DO NOT USE**) -.. py:data:: OPT_X_TLS - - .. deprecated:: 3.3.0 - The option is deprecated in OpenLDAP and should no longer be used. It - will be removed in the future. - .. note:: OpenLDAP supports several TLS/SSL libraries. OpenSSL is the most common @@ -923,11 +958,6 @@ and wait for and return with the server's result, or with The *dn* and *attr* arguments are text strings; see :ref:`bytes_mode`. - .. note:: - - A design fault in the LDAP API prevents *value* - from containing *NULL* characters. - .. py:method:: LDAPObject.delete(dn) -> int diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 71994a8d..1807fc55 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -267,8 +267,6 @@ class Str(Constant): Int('OPT_DEFBASE', optional=True), - TLSInt('OPT_X_TLS', optional=True), - TLSInt('OPT_X_TLS_CTX'), TLSInt('OPT_X_TLS_CACERTFILE'), TLSInt('OPT_X_TLS_CACERTDIR'), TLSInt('OPT_X_TLS_CERTFILE'), @@ -306,6 +304,13 @@ class Str(Constant): # Added in OpenLDAP 2.5 TLSInt('OPT_X_TLS_PEERCERT', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_MAX', optional=True), + + TLSInt('OPT_X_TLS_PROTOCOL_SSL3', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_0', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_1', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_2', optional=True), + TLSInt('OPT_X_TLS_PROTOCOL_TLS1_3', optional=True), Int('OPT_X_SASL_MECH'), Int('OPT_X_SASL_REALM'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 9df264ed..2d385549 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -202,12 +202,6 @@ add_int(OPT_DEFBASE); #if HAVE_TLS - -#if defined(LDAP_OPT_X_TLS) -add_int(OPT_X_TLS); -#endif - -add_int(OPT_X_TLS_CTX); add_int(OPT_X_TLS_CACERTFILE); add_int(OPT_X_TLS_CACERTDIR); add_int(OPT_X_TLS_CERTFILE); @@ -279,6 +273,36 @@ add_int(OPT_X_TLS_REQUIRE_SAN); add_int(OPT_X_TLS_PEERCERT); #endif + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_MAX) +add_int(OPT_X_TLS_PROTOCOL_MAX); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_SSL3) +add_int(OPT_X_TLS_PROTOCOL_SSL3); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_0) +add_int(OPT_X_TLS_PROTOCOL_TLS1_0); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_1) +add_int(OPT_X_TLS_PROTOCOL_TLS1_1); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_2) +add_int(OPT_X_TLS_PROTOCOL_TLS1_2); +#endif + + +#if defined(LDAP_OPT_X_TLS_PROTOCOL_TLS1_3) +add_int(OPT_X_TLS_PROTOCOL_TLS1_3); +#endif + #endif add_int(OPT_X_SASL_MECH); diff --git a/Modules/options.c b/Modules/options.c index ef9eddf2..1a22bed1 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -98,6 +98,9 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + case LDAP_OPT_X_TLS_PROTOCOL_MAX: +#endif #ifdef LDAP_OPT_X_TLS_REQUIRE_SAN case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif @@ -344,6 +347,9 @@ LDAP_get_option(LDAPObject *self, int option) #ifdef LDAP_OPT_X_TLS_PROTOCOL_MIN case LDAP_OPT_X_TLS_PROTOCOL_MIN: #endif +#ifdef LDAP_OPT_X_TLS_PROTOCOL_MAX + case LDAP_OPT_X_TLS_PROTOCOL_MAX: +#endif #ifdef LDAP_OPT_X_TLS_REQUIRE_SAN case LDAP_OPT_X_TLS_REQUIRE_SAN: #endif From febaf561b241a0d0cbe8be6ea5f786f4276c30ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Thu, 27 Jan 2022 10:35:56 +0000 Subject: [PATCH 40/73] Fix omissions from previous merge. --- Doc/reference/ldap.rst | 4 ++-- setup.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 9bd6b142..5d4fb642 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -349,7 +349,7 @@ TLS options .. py:data:: OPT_X_TLS_REQUIRE_SAN get/set how OpenLDAP validates subject alternative name extension, - available in OpenSSL 2.4.52 and newer. + available in OpenLDAP 2.4.52 and newer. :py:const:`OPT_X_TLS_NEVER` Don't check SAN @@ -422,7 +422,7 @@ TLS options .. py:data:: OPT_X_TLS_PROTOCOL_MAX get/set maximum protocol version (wire protocol version as int), - available in OpenSSL 2.5 and newer. + available in OpenLDAP 2.5 and newer. .. versionadded:: 3.4.1 diff --git a/setup.cfg b/setup.cfg index 48f36197..fdb32fbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,7 +16,7 @@ license_file = LICENCE # These defines needs OpenLDAP built with # ./configure --with-cyrus-sasl --with-tls -defines = HAVE_SASL HAVE_TLS HAVE_LIBLDAP_R +defines = HAVE_SASL HAVE_TLS extra_compile_args = extra_objects = From 8666af380ef8dde9973f46fb20e458f7eab6ce9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 6 Apr 2022 11:11:08 +0100 Subject: [PATCH 41/73] Update to behera ppolicy draft 11 --- Lib/ldap/controls/ppolicy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/ldap/controls/ppolicy.py b/Lib/ldap/controls/ppolicy.py index da7586f0..f3a8416d 100644 --- a/Lib/ldap/controls/ppolicy.py +++ b/Lib/ldap/controls/ppolicy.py @@ -40,9 +40,10 @@ class PasswordPolicyError(univ.Enumerated): ('insufficientPasswordQuality',5), ('passwordTooShort',6), ('passwordTooYoung',7), - ('passwordInHistory',8) + ('passwordInHistory',8), + ('passwordTooLong',9), ) - subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8) + subtypeSpec = univ.Enumerated.subtypeSpec + constraint.SingleValueConstraint(0,1,2,3,4,5,6,7,8,9) class PasswordPolicyResponseValue(univ.Sequence): From 7c25278259952e4787e86d20934af71fc17c197d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 20 Apr 2022 15:38:51 +0100 Subject: [PATCH 42/73] Make 'method' parameter of ReconnectLDAPObject._store_last_bind private Fixes: https://github.com/python-ldap/python-ldap/issues/448 --- 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 40091ad7..9442e39b 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -895,8 +895,8 @@ def __setstate__(self,d): self._trace_file = ldap._trace_file self.reconnect(self._uri) - def _store_last_bind(self,method,*args,**kwargs): - self._last_bind = (method,args,kwargs) + def _store_last_bind(self,_method,*args,**kwargs): + self._last_bind = (_method,args,kwargs) def _apply_last_bind(self): if self._last_bind!=None: From 7f30c4721ea2ca4373ed7860e6467781f0afa758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 20 Apr 2022 14:42:42 +0100 Subject: [PATCH 43/73] Document OPT_X_SASL_* differ from others Fixes: https://github.com/python-ldap/python-ldap/issues/468 --- Doc/reference/ldap.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 5d4fb642..0046f4a1 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -226,6 +226,9 @@ the following option identifiers are defined as constants: SASL options :::::::::::: +Unlike most other options, SASL options must be set on an +:py:class:`LDAPObject` instance. + .. py:data:: OPT_X_SASL_AUTHCID .. py:data:: OPT_X_SASL_AUTHZID @@ -234,7 +237,8 @@ SASL options .. py:data:: OPT_X_SASL_NOCANON - If set to zero SASL host name canonicalization is disabled. + If set to zero, SASL host name canonicalization is disabled. This is the only + SASL option that can be set globally. .. py:data:: OPT_X_SASL_REALM From 81e1f28c15c86ddc48eddca39fdf64273809587d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Jun 2022 09:39:55 +0100 Subject: [PATCH 44/73] Correct SASL option documentation --- Doc/reference/ldap.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 0046f4a1..2d5c4780 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -237,8 +237,7 @@ Unlike most other options, SASL options must be set on an .. py:data:: OPT_X_SASL_NOCANON - If set to zero, SASL host name canonicalization is disabled. This is the only - SASL option that can be set globally. + If set to zero, SASL host name canonicalization is disabled. .. py:data:: OPT_X_SASL_REALM From 59af061d1bc2fad832952f0b18105ab1ae18246d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Jun 2022 10:53:14 +0100 Subject: [PATCH 45/73] Prepare CHANGELOG --- CHANGES | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGES b/CHANGES index c358fa9e..8f9237a8 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,43 @@ +Released 3.4.1 2022-07-05 + +This is a minor release to provide out-of-the-box compatibility with the merge +of libldap and libldap_r that happened with OpenLDAP's 2.5 release. + +The following undocumented functions are deprecated and scheduled for removal: +- ``ldap.cidict.strlist_intersection`` +- ``ldap.cidict.strlist_minus`` +- ``ldap.cidict.strlist_union`` + +The following deprecated option has been removed: +- ``OPT_X_TLS`` + +Doc/ +* SASL option usage has been clarified + +Lib/ +* ppolicy control definition has been updated to match Behera draft 11 + +Modules/ +* By default, compile against libldap, checking whether it provides a + threadsafe implementation at runtime +* When decoding controls, the module can now distinguish between no value + (now exposed as ``None``) and an empty value (exposed as ``b''``) +* Several new OpenLDAP options are now supported: + * ``OPT_SOCKET_BIND_ADDRESSES`` + * ``OPT_TCP_USER_TIMEOUT`` + * ``OPT_X_SASL_MAXBUFSIZE`` + * ``OPT_X_SASL_SECPROPS`` + * ``OPT_X_TLS_ECNAME`` + * ``OPT_X_TLS_PEERCERT`` + * ``OPT_X_TLS_PROTOCOL``-related options and constants + +Fixes: +* Encoding/decoding of boolean controls has been corrected +* ldap.schema.models.Entry is now usable +* ``method`` keyword to ReconnectLDAPObject.bind_s is now usable + + +---------------------------------------------------------------- Released 3.4.0 2021-11-26 This release requires Python 3.6 or above, From b80e8135785ef80a40a52eb61033a54dfd70266e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Tue, 5 Jul 2022 15:29:24 +0100 Subject: [PATCH 46/73] Prepare a new release --- CHANGES | 2 +- Doc/contributing.rst | 2 ++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGES b/CHANGES index 8f9237a8..b1ccc990 100644 --- a/CHANGES +++ b/CHANGES @@ -1,4 +1,4 @@ -Released 3.4.1 2022-07-05 +Released 3.4.2 2022-07-06 This is a minor release to provide out-of-the-box compatibility with the merge of libldap and libldap_r that happened with OpenLDAP's 2.5 release. diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 1fc1365b..bbaab491 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -218,6 +218,8 @@ If you are tasked with releasing python-ldap, remember to: * Go through all changes since last version, and add them to ``CHANGES``. * Run :ref:`additional tests` as appropriate, fix any regressions. * Change the release date in ``CHANGES``. +* Update ``__version__`` tags where appropriate (each module ``ldap``, + ``ldif``, ``ldapurl``, ``slapdtest`` has its own copy). * Merge all that (using pull requests). * Run ``python setup.py sdist``, and smoke-test the resulting package (install in a clean virtual environment, import ``ldap``). diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index ef958a13..4e195264 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.0' +__version__ = '3.4.2' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index cce4e806..e76528a7 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 7e69a594..7561d09a 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index bb59e7fa..a49b13f7 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.0' +__version__ = '3.4.2' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 00eaedd09ce5a7dabb4bc337443e020771177fa4 Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Fri, 26 Aug 2022 23:03:38 +0200 Subject: [PATCH 47/73] Add ASN.1 replace to psearch control Allow Sphinx to pickup the ASN.1 substitution from pyasn1, so docs can build. --- Doc/reference/ldap-controls.rst | 1 + Doc/reference/ldap-extop.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Doc/reference/ldap-controls.rst b/Doc/reference/ldap-controls.rst index 37d7c1bc..2206e101 100644 --- a/Doc/reference/ldap-controls.rst +++ b/Doc/reference/ldap-controls.rst @@ -171,6 +171,7 @@ search. .. autoclass:: ldap.controls.psearch.EntryChangeNotificationControl :members: +.. |ASN.1| replace:: Asn1Type :py:mod:`ldap.controls.sessiontrack` Session tracking control ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/reference/ldap-extop.rst b/Doc/reference/ldap-extop.rst index 8fe49f42..ad70e4e7 100644 --- a/Doc/reference/ldap-extop.rst +++ b/Doc/reference/ldap-extop.rst @@ -38,3 +38,5 @@ This requires :py:mod:`pyasn1` and :py:mod:`pyasn1_modules` to be installed. .. autoclass:: ldap.extop.dds.RefreshResponse :members: + +.. |ASN.1| replace:: Asn1Type From e5959b38902bbf1d68a3f16b04ddc210884b8d5e Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Sat, 27 Aug 2022 19:40:46 +0200 Subject: [PATCH 48/73] Fix broken reset and tearDown in pypy3 There appear to be some minor differences in the CPython and Pypy3 implementations of del. Use delattr to ensure that hasattr will see the attributes as deleted. --- Tests/t_ldapobject.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index 9e4e3311..ccc7d218 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -658,12 +658,12 @@ def _open_ldap_conn(self, who=None, cred=None, **kwargs): def tearDown(self): self._sock.close() - del self._sock + delattr(self, '_sock') super().tearDown() def reset_connection(self): self._sock.close() - del self._sock + delattr(self, '_sock') super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection() From 4e53fc927945e294d74cfde1f0e66dee4327637d Mon Sep 17 00:00:00 2001 From: Simon Lyngshede Date: Wed, 31 Aug 2022 20:11:13 +0200 Subject: [PATCH 49/73] Add slapdtest.certs to setup.py packages to fix the deprecation warning --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b1939571..33cc2603 100644 --- a/setup.py +++ b/setup.py @@ -153,6 +153,7 @@ class OpenLDAP2: 'ldap.extop', 'ldap.schema', 'slapdtest', + 'slapdtest.certs', ], package_dir = {'': 'Lib',}, data_files = LDAP_CLASS.extra_files, From b3d6a8c2a6466dcf5f24cfa42d2bfc3815f9cedd Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 1 Sep 2022 11:32:03 -0700 Subject: [PATCH 50/73] Back out the removal of OPT_X_TLS and OPT_X_TLS_CTX options Closes #480 --- Doc/reference/ldap.rst | 10 ++++++++++ Lib/ldap/constants.py | 2 ++ Modules/constants_generated.h | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index 2d5c4780..d059dfa4 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -409,6 +409,10 @@ TLS options get/set allowed cipher suites +.. py:data:: OPT_X_TLS_CTX + + get address of internal memory address of TLS context (**DO NOT USE**) + .. py:data:: OPT_X_TLS_PEERCERT Get peer's certificate as binary ASN.1 data structure (DER) @@ -472,6 +476,12 @@ TLS options get/set path to /dev/urandom (**DO NOT USE**) +.. py:data:: OPT_X_TLS + + .. deprecated:: 3.3.0 + The option is deprecated in OpenLDAP and should no longer be used. It + will be removed in the future. + .. note:: OpenLDAP supports several TLS/SSL libraries. OpenSSL is the most common diff --git a/Lib/ldap/constants.py b/Lib/ldap/constants.py index 1807fc55..0e7df6e7 100644 --- a/Lib/ldap/constants.py +++ b/Lib/ldap/constants.py @@ -267,6 +267,8 @@ class Str(Constant): Int('OPT_DEFBASE', optional=True), + TLSInt('OPT_X_TLS', optional=True), + TLSInt('OPT_X_TLS_CTX'), TLSInt('OPT_X_TLS_CACERTFILE'), TLSInt('OPT_X_TLS_CACERTDIR'), TLSInt('OPT_X_TLS_CERTFILE'), diff --git a/Modules/constants_generated.h b/Modules/constants_generated.h index 2d385549..3e59f828 100644 --- a/Modules/constants_generated.h +++ b/Modules/constants_generated.h @@ -202,6 +202,12 @@ add_int(OPT_DEFBASE); #if HAVE_TLS + +#if defined(LDAP_OPT_X_TLS) +add_int(OPT_X_TLS); +#endif + +add_int(OPT_X_TLS_CTX); add_int(OPT_X_TLS_CACERTFILE); add_int(OPT_X_TLS_CACERTDIR); add_int(OPT_X_TLS_CERTFILE); From 9dd59a9cefdf8980beca5d75ac5983fdde7f0d79 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 15 Sep 2022 16:38:25 -0700 Subject: [PATCH 51/73] Prepare a new release --- CHANGES | 15 +++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index b1ccc990..500fa1e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,18 @@ +Released 3.4.3 2022-09-15 + +This is a minor release to bring back the removed OPT_X_TLS option. +Please note, it's still a deprecated option and it will be removed in 3.5.0. + +The following deprecated option has been brought back: +- ``OPT_X_TLS`` + +Fixes: +* Sphinx documentation is now successfully built +* pypy3 tests stability was improved +* setup.py deprecation warning is now resolved + + +---------------------------------------------------------------- Released 3.4.2 2022-07-06 This is a minor release to provide out-of-the-box compatibility with the merge diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 4e195264..026e9101 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.2' +__version__ = '3.4.3' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index e76528a7..964076d3 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index 7561d09a..ae1d643d 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index a49b13f7..7ab7d2bd 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.2' +__version__ = '3.4.3' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From 88e74b923594693f9d6a7f666f1ce783f2f39762 Mon Sep 17 00:00:00 2001 From: Josh Thomas Date: Tue, 20 Dec 2022 20:58:16 -0600 Subject: [PATCH 52/73] Add Python 3.10 and 3.11 to CI and tox --- .github/workflows/ci.yml | 2 +- .github/workflows/tox-fedora.yml | 1 + setup.py | 2 ++ tox.ini | 4 +++- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bc2ae0e..9700a8ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9, pypy3] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] steps: - name: Checkout uses: "actions/checkout@v2" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index c0dbb45c..cc75db90 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -23,6 +23,7 @@ jobs: - py38 - py39 - py310 + - py311 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/setup.py b/setup.py index 33cc2603..2bba473e 100644 --- a/setup.py +++ b/setup.py @@ -90,6 +90,8 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index aaef8b5a..3387d094 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 +envlist = py{36,37,38,39,310,311},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 minver = 1.8 [gh-actions] @@ -14,6 +14,8 @@ python = 3.7: py37 3.8: py38, doc, py3-nosasltls 3.9: py39, py3-trace + 3.10: py310 + 3.11: py311 pypy3: pypy3 [testenv] From b8ad0c5ea7d2c4b295ea636c03124c9e5efb5eea Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Wed, 18 Jan 2023 19:17:00 -0800 Subject: [PATCH 53/73] Fix CI images after ubuntu-latest update Keep 20.04 for py3.6 testing. Replace the deprecated enchant package with enchant-2. --- .github/workflows/ci.yml | 4 ++-- .github/workflows/tox-fedora.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9700a8ed..2432e9e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: [push, pull_request] jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" - runs-on: "ubuntu-latest" + runs-on: "ubuntu-20.04" strategy: fail-fast: false matrix: @@ -18,7 +18,7 @@ jobs: run: | set -ex sudo apt update - sudo apt install -y ldap-utils slapd enchant libldap2-dev libsasl2-dev apparmor-utils + sudo apt install -y ldap-utils slapd enchant-2 libldap2-dev libsasl2-dev apparmor-utils - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index cc75db90..381a0b0c 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -32,4 +32,4 @@ jobs: - doc # Use GitHub's Linux Docker host - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 From ec74e5b0a60aa5716119aa06ac48e01344b5fdf2 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 2 Feb 2023 21:34:08 -0800 Subject: [PATCH 54/73] Conscious Language: Rename master branch to main branch Additionally, rename sphinx configuration parameter master_doc to root_doc Fixes: https://github.com/python-ldap/python-ldap/issues/509 --- Doc/conf.py | 4 ++-- Doc/faq.rst | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/conf.py b/Doc/conf.py index b883736e..e79cfb34 100644 --- a/Doc/conf.py +++ b/Doc/conf.py @@ -50,8 +50,8 @@ # The suffix of source filenames. source_suffix = '.rst' -# The master toctree document. -master_doc = 'index' +# The root toctree document. +root_doc = 'index' # General substitutions. project = 'python-ldap' diff --git a/Doc/faq.rst b/Doc/faq.rst index e96fc030..39a6743c 100644 --- a/Doc/faq.rst +++ b/Doc/faq.rst @@ -13,7 +13,7 @@ Project **A3**: see file CHANGES in source distribution or `repository`_. -.. _repository: https://github.com/python-ldap/python-ldap/blob/master/CHANGES +.. _repository: https://github.com/python-ldap/python-ldap/blob/main/CHANGES Usage From 105925fe6db9cc6c71fafe16bddaa4abcbb8ddce Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Wed, 8 Feb 2023 02:21:53 +0100 Subject: [PATCH 55/73] fix(ReconnectLDAPObject): reconnect race condition Move calling `unbind_s()` inside the locked region so that `self._l` is handled atomically. Add a new parameter `force` to either forcefully close any previous connection or keep re-use the previous connection if it still is supposed to work. --- Lib/ldap/ldapobject.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 9442e39b..7a9c17f6 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -893,7 +893,7 @@ def __setstate__(self,d): self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self))) # XXX cannot pickle file, use default trace file self._trace_file = ldap._trace_file - self.reconnect(self._uri) + self.reconnect(self._uri,force=True) def _store_last_bind(self,_method,*args,**kwargs): self._last_bind = (_method,args,kwargs) @@ -914,11 +914,16 @@ def _restore_options(self): def passwd_s(self,*args,**kwargs): return self._apply_method_s(SimpleLDAPObject.passwd_s,*args,**kwargs) - def reconnect(self,uri,retry_max=1,retry_delay=60.0): + def reconnect(self,uri,retry_max=1,retry_delay=60.0,force=True): # Drop and clean up old connection completely # Reconnect self._reconnect_lock.acquire() try: + if hasattr(self,'_l'): + if force: + SimpleLDAPObject.unbind_s(self) + else: + return reconnect_counter = retry_max while reconnect_counter: counter_text = '%d. (of %d)' % (retry_max-reconnect_counter+1,retry_max) @@ -962,14 +967,12 @@ def reconnect(self,uri,retry_max=1,retry_delay=60.0): return # reconnect() def _apply_method_s(self,func,*args,**kwargs): - if not hasattr(self,'_l'): - self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) + self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=False) try: return func(self,*args,**kwargs) except ldap.SERVER_DOWN: - SimpleLDAPObject.unbind_s(self) # Try to reconnect - self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay) + self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay,force=True) # Re-try last operation return func(self,*args,**kwargs) From e756eac00a80240b9c708fba145ff664c134b957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lum=C3=ADr=20=27Frenzy=27=20Balhar?= Date: Fri, 14 Apr 2023 06:13:20 +0200 Subject: [PATCH 56/73] Switch tox-github-action from master to main branch --- .github/workflows/tox-fedora.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 381a0b0c..4e88cee4 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -8,7 +8,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run Tox tests - uses: fedora-python/tox-github-action@master + uses: fedora-python/tox-github-action@main with: tox_env: ${{ matrix.tox_env }} dnf_install: > From 91e0918f822e5a28d5b66b0bdf9b99b596f5be3c Mon Sep 17 00:00:00 2001 From: Diogo Teles Sant'Anna Date: Fri, 2 Jun 2023 12:46:09 -0300 Subject: [PATCH 57/73] ci: set minimal permissions on workflows (#525) Signed-off-by: Diogo Teles Sant'Anna --- .github/workflows/ci.yml | 3 +++ .github/workflows/tox-fedora.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2432e9e8..86e8ba51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,9 @@ name: CI on: [push, pull_request] +permissions: + contents: read + jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 4e88cee4..f41024a0 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -2,6 +2,9 @@ on: [push, pull_request] name: Tox on Fedora +permissions: + contents: read + jobs: tox_test: name: Tox env "${{matrix.tox_env}}" on Fedora From 72c1b5e0f37f74b1a68e67b6b5712d395d577bb9 Mon Sep 17 00:00:00 2001 From: Diogo Teles Sant'Anna Date: Thu, 27 Jul 2023 20:55:14 -0300 Subject: [PATCH 58/73] docs: create Security Policy (#530) --- SECURITY.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..752b1394 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +Security updates are applied only to the latest release. + +## Reporting a Vulnerability + +If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. + +Please disclose it at our [security advisory](https://github.com/python-ldap/python-ldap/security/advisories/new). + +This project is maintained by a team of volunteers on a reasonable-effort basis. As such, vulnerabilities will be disclosed in a best effort base. From 30fe146e6c8e881a2db2a3f6b60fb6201bf6a534 Mon Sep 17 00:00:00 2001 From: Chris Lenk Date: Tue, 22 Aug 2023 20:02:46 -0400 Subject: [PATCH 59/73] Update article links in resources.rst (#533) Fix broken links. --- Doc/resources.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/resources.rst b/Doc/resources.rst index 56cb1a1a..795f8b63 100644 --- a/Doc/resources.rst +++ b/Doc/resources.rst @@ -8,13 +8,13 @@ members. Therefore some information might be outdated or links might be broken. *Python LDAP Applications* articles by Matt Butcher --------------------------------------------------- -* `Part 1 - Installing and Configuring the Python-LDAP Library and Binding to an LDAP Directory `_ +* `Part 1 - Installing and Configuring the Python-LDAP Library and Binding to an LDAP Directory `_ This also covers SASL. -* `Part 2 - LDAP Operations `_ -* `Part 3 - More LDAP Operations and the LDAP URL Library `_ -* `Part 4 - LDAP Schema `_ +* `Part 2 - LDAP Operations `_ +* `Part 3 - More LDAP Operations and the LDAP URL Library `_ +* `Part 4 - LDAP Schema `_ Gee, someone waded through the badly documented mysteries of module :mod:`ldap.schema`. From 2229d83646895ce041a5582400fa77e82d40c2c7 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Wed, 4 Oct 2023 18:42:53 +0200 Subject: [PATCH 60/73] Test with Python 3.12 (#537) --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++----- .github/workflows/tox-fedora.yml | 1 + Modules/options.c | 4 ++-- setup.py | 1 + tox.ini | 10 ++++++++-- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86e8ba51..37843f31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,13 @@ --- name: CI -on: [push, pull_request] +on: + push: + pull_request: + schedule: + # every Monday + - cron: '30 4 * * 1' + workflow_dispatch: permissions: contents: read @@ -9,14 +15,26 @@ permissions: jobs: distros: name: "Ubuntu with Python ${{ matrix.python-version }}" - runs-on: "ubuntu-20.04" + runs-on: "${{ matrix.image }}" strategy: fail-fast: false matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "pypy3"] + python-version: + - "3.7" + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "pypy3.9" + image: + - "ubuntu-22.04" + include: + - python-version: "3.6" + image: "ubuntu-20.04" steps: - name: Checkout - uses: "actions/checkout@v2" + uses: "actions/checkout@v4" - name: Install apt dependencies run: | set -ex @@ -25,9 +43,10 @@ jobs: - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: "Install Python dependencies" run: | set -xe diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index f41024a0..b86303fe 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -27,6 +27,7 @@ jobs: - py39 - py310 - py311 + - py312 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/Modules/options.c b/Modules/options.c index 1a22bed1..a621f81a 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -207,8 +207,8 @@ LDAP_set_option(LDAPObject *self, int option, PyObject *value) } else { PyErr_Format(PyExc_ValueError, - "timeout must be >= 0 or -1/None for infinity, got %f", - doubleval); + "timeout must be >= 0 or -1/None for infinity, got %S", + value); return 0; } break; diff --git a/setup.py b/setup.py index 2bba473e..6da3f491 100644 --- a/setup.py +++ b/setup.py @@ -92,6 +92,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index 3387d094..beade024 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310,311},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3 +envlist = py{36,37,38,39,310,311,312},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3.9 minver = 1.8 [gh-actions] @@ -16,7 +16,8 @@ python = 3.9: py39, py3-trace 3.10: py310 3.11: py311 - pypy3: pypy3 + 3.12: py312 + pypy3.9: pypy3.9 [testenv] deps = @@ -28,6 +29,11 @@ setenv = commands = {envpython} -bb -Werror \ -m unittest discover -v -s Tests -p 't_*' {posargs} +[testenv:py312] +# Python 3.12 headers are incompatible with declaration-after-statement +setenv = + CFLAGS=-Wno-int-in-bool-context -Werror -std=c99 + [testenv:py3-nosasltls] basepython = python3 # don't install, install dependencies manually From 1490e999e10960f0fdf974f6da804a2fc55e4b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Kuzn=C3=ADk?= Date: Wed, 1 Nov 2023 17:46:35 +0000 Subject: [PATCH 61/73] Claim ownership of socket once we've passed it to libldap Fixes: https://github.com/python-ldap/python-ldap/issues/460 Closes: https://github.com/python-ldap/python-ldap/pull/543 --- Tests/t_ldapobject.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/Tests/t_ldapobject.py b/Tests/t_ldapobject.py index ccc7d218..ada5f990 100644 --- a/Tests/t_ldapobject.py +++ b/Tests/t_ldapobject.py @@ -647,24 +647,14 @@ def test105_reconnect_restore(self): @requires_init_fd() class Test03_SimpleLDAPObjectWithFileno(Test00_SimpleLDAPObject): def _open_ldap_conn(self, who=None, cred=None, **kwargs): - if hasattr(self, '_sock'): - raise RuntimeError("socket already connected") - self._sock = socket.create_connection( + sock = socket.create_connection( (self.server.hostname, self.server.port) ) - return super()._open_ldap_conn( - who=who, cred=cred, fileno=self._sock.fileno(), **kwargs + result = super()._open_ldap_conn( + who=who, cred=cred, fileno=sock.fileno(), **kwargs ) - - def tearDown(self): - self._sock.close() - delattr(self, '_sock') - super().tearDown() - - def reset_connection(self): - self._sock.close() - delattr(self, '_sock') - super(Test03_SimpleLDAPObjectWithFileno, self).reset_connection() + sock.detach() + return result if __name__ == '__main__': From 75a765f82fed2e3f418a1152b3af02f7d1e2629e Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 30 Oct 2023 09:19:41 +0100 Subject: [PATCH 62/73] refactor: Merge all header files Merge all header files except `constants_generated.h` into a single header file `pythonldap.h`. A single header file makes it far easier to port python-ldap to heap types and module state for Per-Interpreter GIL. `pythonldap.h` uses new macros `PYLDAP_FUNC` and `PYLDAP_DATA` to declare functions and data, which are used across C files. Remove unused macro `streq`. See: https://github.com/python-ldap/python-ldap/issues/540 Signed-off-by: Christian Heimes --- Makefile | 3 +- Modules/LDAPObject.c | 8 +-- Modules/LDAPObject.h | 38 ------------ Modules/berval.c | 3 +- Modules/berval.h | 11 ---- Modules/common.c | 2 +- Modules/common.h | 68 --------------------- Modules/constants.c | 4 +- Modules/constants.h | 24 -------- Modules/functions.c | 7 +-- Modules/functions.h | 9 --- Modules/ldapcontrol.c | 6 +- Modules/ldapcontrol.h | 13 ---- Modules/ldapmodule.c | 7 +-- Modules/message.c | 6 +- Modules/message.h | 11 ---- Modules/options.c | 7 +-- Modules/options.h | 7 --- Modules/pythonldap.h | 137 ++++++++++++++++++++++++++++++++++++++++++ setup.py | 9 +-- 20 files changed, 149 insertions(+), 231 deletions(-) delete mode 100644 Modules/LDAPObject.h delete mode 100644 Modules/berval.h delete mode 100644 Modules/common.h delete mode 100644 Modules/constants.h delete mode 100644 Modules/functions.h delete mode 100644 Modules/ldapcontrol.h delete mode 100644 Modules/message.h delete mode 100644 Modules/options.h create mode 100644 Modules/pythonldap.h diff --git a/Makefile b/Makefile index 577ba883..2b52ddf5 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,8 @@ valgrind: build $(PYTHON_SUPP) autoformat: indent black indent: - indent Modules/*.c Modules/*.h + indent Modules/*.c + indent -npsl Modules/pythonldap.h rm -f Modules/*.c~ Modules/*.h~ black: diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index da18d575..eaf831bd 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -1,16 +1,10 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" +#include "pythonldap.h" #include "patchlevel.h" #include #include -#include "constants.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "message.h" -#include "berval.h" -#include "options.h" #ifdef HAVE_SASL #include diff --git a/Modules/LDAPObject.h b/Modules/LDAPObject.h deleted file mode 100644 index 4af0b382..00000000 --- a/Modules/LDAPObject.h +++ /dev/null @@ -1,38 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_LDAPObject -#define __h_LDAPObject - -#include "common.h" - -typedef struct { - PyObject_HEAD LDAP *ldap; - PyThreadState *_save; /* for thread saving on referrals */ - int valid; -} LDAPObject; - -extern PyTypeObject LDAP_Type; - -#define LDAPObject_Check(v) (Py_TYPE(v) == &LDAP_Type) - -extern LDAPObject *newLDAPObject(LDAP *); - -/* macros to allow thread saving in the context of an LDAP connection */ - -#define LDAP_BEGIN_ALLOW_THREADS( l ) \ - { \ - LDAPObject *lo = (l); \ - if (lo->_save != NULL) \ - Py_FatalError( "saving thread twice?" ); \ - lo->_save = PyEval_SaveThread(); \ - } - -#define LDAP_END_ALLOW_THREADS( l ) \ - { \ - LDAPObject *lo = (l); \ - PyThreadState *_save = lo->_save; \ - lo->_save = NULL; \ - PyEval_RestoreThread( _save ); \ - } - -#endif /* __h_LDAPObject */ diff --git a/Modules/berval.c b/Modules/berval.c index 6917baef..39cc98a8 100644 --- a/Modules/berval.c +++ b/Modules/berval.c @@ -1,7 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "berval.h" +#include "pythonldap.h" /* * Copies out the data from a berval, and returns it as a new Python object, diff --git a/Modules/berval.h b/Modules/berval.h deleted file mode 100644 index 9c427240..00000000 --- a/Modules/berval.h +++ /dev/null @@ -1,11 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_berval -#define __h_berval - -#include "common.h" - -PyObject *LDAPberval_to_object(const struct berval *bv); -PyObject *LDAPberval_to_unicode_object(const struct berval *bv); - -#endif /* __h_berval_ */ diff --git a/Modules/common.c b/Modules/common.c index 9d7001c0..4cfee744 100644 --- a/Modules/common.c +++ b/Modules/common.c @@ -1,7 +1,7 @@ /* Miscellaneous common routines * See https://www.python-ldap.org/ for details. */ -#include "common.h" +#include "pythonldap.h" /* dynamically add the methods into the module dictionary d */ diff --git a/Modules/common.h b/Modules/common.h deleted file mode 100644 index bc554c85..00000000 --- a/Modules/common.h +++ /dev/null @@ -1,68 +0,0 @@ -/* common utility macros - * See https://www.python-ldap.org/ for details. */ - -#ifndef __h_common -#define __h_common - -#define PY_SSIZE_T_CLEAN - -#include "Python.h" - -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif - -#include -#include -#include - -#if LDAP_VENDOR_VERSION < 20400 -#error Current python-ldap requires OpenLDAP 2.4.x -#endif - -#if LDAP_VENDOR_VERSION >= 20448 - /* openldap.h with ldap_init_fd() was introduced in 2.4.48 - * see https://bugs.openldap.org/show_bug.cgi?id=8671 - */ -#define HAVE_LDAP_INIT_FD 1 -#include -#elif (defined(__APPLE__) && (LDAP_VENDOR_VERSION == 20428)) -/* macOS system libldap 2.4.28 does not have ldap_init_fd symbol */ -#undef HAVE_LDAP_INIT_FD -#else - /* ldap_init_fd() has been around for a very long time - * SSSD has been defining the function for a while, so it's probably OK. - */ -#define HAVE_LDAP_INIT_FD 1 -#define LDAP_PROTO_TCP 1 -#define LDAP_PROTO_UDP 2 -#define LDAP_PROTO_IPC 3 -extern int ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, - LDAP **ldp); -#endif - -#if defined(MS_WINDOWS) -#include -#else /* unix */ -#include -#include -#include -#endif - -#include -#define streq( a, b ) \ - ( (*(a)==*(b)) && 0==strcmp(a,b) ) - -extern PyObject *LDAPerror_TypeError(const char *, PyObject *); - -void LDAPadd_methods(PyObject *d, PyMethodDef *methods); - -#define PyNone_Check(o) ((o) == Py_None) - -/* Py2/3 compatibility */ -#if PY_VERSION_HEX >= 0x03000000 -/* In Python 3, alias PyInt to PyLong */ -#define PyInt_FromLong PyLong_FromLong -#endif - -#endif /* __h_common_ */ diff --git a/Modules/constants.c b/Modules/constants.c index 8d6f63b0..b70db245 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -1,9 +1,7 @@ /* constants defined for LDAP * See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "ldapcontrol.h" +#include "pythonldap.h" /* the base exception class */ diff --git a/Modules/constants.h b/Modules/constants.h deleted file mode 100644 index 7b9ce53e..00000000 --- a/Modules/constants.h +++ /dev/null @@ -1,24 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_constants_ -#define __h_constants_ - -#include "common.h" - -extern int LDAPinit_constants(PyObject *m); -extern PyObject *LDAPconstant(int); - -extern PyObject *LDAPexception_class; -extern PyObject *LDAPerror(LDAP *); -extern PyObject *LDAPraise_for_message(LDAP *, LDAPMessage *m); -PyObject *LDAPerr(int errnum); - -#ifndef LDAP_CONTROL_PAGE_OID -#define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" -#endif /* !LDAP_CONTROL_PAGE_OID */ - -#ifndef LDAP_CONTROL_VALUESRETURNFILTER -#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ -#endif /* !LDAP_CONTROL_VALUESRETURNFILTER */ - -#endif /* __h_constants_ */ diff --git a/Modules/functions.c b/Modules/functions.c index b811708f..f7d9cf37 100644 --- a/Modules/functions.c +++ b/Modules/functions.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "functions.h" -#include "LDAPObject.h" -#include "berval.h" -#include "constants.h" -#include "options.h" +#include "pythonldap.h" /* ldap_initialize */ diff --git a/Modules/functions.h b/Modules/functions.h deleted file mode 100644 index 2aef9740..00000000 --- a/Modules/functions.h +++ /dev/null @@ -1,9 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_functions_ -#define __h_functions_ - -#include "common.h" -extern void LDAPinit_functions(PyObject *); - -#endif /* __h_functions_ */ diff --git a/Modules/ldapcontrol.c b/Modules/ldapcontrol.c index e287e9a3..4a37b614 100644 --- a/Modules/ldapcontrol.c +++ b/Modules/ldapcontrol.c @@ -1,10 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "berval.h" -#include "constants.h" +#include "pythonldap.h" /* Prints to stdout the contents of an array of LDAPControl objects */ diff --git a/Modules/ldapcontrol.h b/Modules/ldapcontrol.h deleted file mode 100644 index 74cae423..00000000 --- a/Modules/ldapcontrol.h +++ /dev/null @@ -1,13 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_ldapcontrol -#define __h_ldapcontrol - -#include "common.h" - -void LDAPinit_control(PyObject *d); -void LDAPControl_List_DEL(LDAPControl **); -int LDAPControls_from_object(PyObject *, LDAPControl ***); -PyObject *LDAPControls_to_List(LDAPControl **ldcs); - -#endif /* __h_ldapcontrol */ diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 34d5a24c..8562337b 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "functions.h" -#include "ldapcontrol.h" - -#include "LDAPObject.h" +#include "pythonldap.h" #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit__ldap(void); diff --git a/Modules/message.c b/Modules/message.c index 22aa313c..f1403237 100644 --- a/Modules/message.c +++ b/Modules/message.c @@ -1,10 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "message.h" -#include "berval.h" -#include "ldapcontrol.h" -#include "constants.h" +#include "pythonldap.h" /* * Converts an LDAP message into a Python structure. diff --git a/Modules/message.h b/Modules/message.h deleted file mode 100644 index ed73f32c..00000000 --- a/Modules/message.h +++ /dev/null @@ -1,11 +0,0 @@ -/* See https://www.python-ldap.org/ for details. */ - -#ifndef __h_message -#define __h_message - -#include "common.h" - -extern PyObject *LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, - int add_intermediates); - -#endif /* __h_message_ */ diff --git a/Modules/options.c b/Modules/options.c index a621f81a..df55ed05 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -1,11 +1,6 @@ /* See https://www.python-ldap.org/ for details. */ -#include "common.h" -#include "constants.h" -#include "LDAPObject.h" -#include "ldapcontrol.h" -#include "options.h" -#include "berval.h" +#include "pythonldap.h" void set_timeval_from_double(struct timeval *tv, double d) diff --git a/Modules/options.h b/Modules/options.h deleted file mode 100644 index fd6a5ce2..00000000 --- a/Modules/options.h +++ /dev/null @@ -1,7 +0,0 @@ -/* 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); -PyObject *LDAP_get_option(LDAPObject *self, int option); - -void set_timeval_from_double(struct timeval *tv, double d); diff --git a/Modules/pythonldap.h b/Modules/pythonldap.h new file mode 100644 index 00000000..ae6a1269 --- /dev/null +++ b/Modules/pythonldap.h @@ -0,0 +1,137 @@ +/* common utility macros + * See https://www.python-ldap.org/ for details. */ + +#ifndef pythonldap_h +#define pythonldap_h + +/* *** common *** */ +#define PY_SSIZE_T_CLEAN + +#include "Python.h" + +#if defined(HAVE_CONFIG_H) +#include "config.h" +#endif + +#include +#include +#include + +/* Py2/3 compatibility */ +#if PY_VERSION_HEX >= 0x03000000 +/* In Python 3, alias PyInt to PyLong */ +#define PyInt_FromLong PyLong_FromLong +#endif + +#if LDAP_VENDOR_VERSION < 20400 +#error Current python-ldap requires OpenLDAP 2.4.x +#endif + +#if LDAP_VENDOR_VERSION >= 20448 + /* openldap.h with ldap_init_fd() was introduced in 2.4.48 + * see https://bugs.openldap.org/show_bug.cgi?id=8671 + */ +#define HAVE_LDAP_INIT_FD 1 +#include +#elif (defined(__APPLE__) && (LDAP_VENDOR_VERSION == 20428)) +/* macOS system libldap 2.4.28 does not have ldap_init_fd symbol */ +#undef HAVE_LDAP_INIT_FD +#else + /* ldap_init_fd() has been around for a very long time + * SSSD has been defining the function for a while, so it's probably OK. + */ +#define HAVE_LDAP_INIT_FD 1 +#define LDAP_PROTO_TCP 1 +#define LDAP_PROTO_UDP 2 +#define LDAP_PROTO_IPC 3 +LDAP_F(int) ldap_init_fd(ber_socket_t fd, int proto, LDAP_CONST char *url, + LDAP **ldp); +#endif + +#if defined(MS_WINDOWS) +#include +#else /* unix */ +#include +#include +#include +#endif + +#define PYLDAP_FUNC(rtype) rtype +#define PYLDAP_DATA(rtype) extern rtype + +PYLDAP_FUNC(PyObject *) LDAPerror_TypeError(const char *, PyObject *); + +PYLDAP_FUNC(void) LDAPadd_methods(PyObject *d, PyMethodDef *methods); + +#define PyNone_Check(o) ((o) == Py_None) + +/* *** berval *** */ +PYLDAP_FUNC(PyObject *) LDAPberval_to_object(const struct berval *bv); +PYLDAP_FUNC(PyObject *) LDAPberval_to_unicode_object(const struct berval *bv); + +/* *** constants *** */ +PYLDAP_FUNC(int) LDAPinit_constants(PyObject *m); + +PYLDAP_DATA(PyObject *) LDAPexception_class; +PYLDAP_FUNC(PyObject *) LDAPerror(LDAP *); +PYLDAP_FUNC(PyObject *) LDAPraise_for_message(LDAP *, LDAPMessage *m); +PYLDAP_FUNC(PyObject *) LDAPerr(int errnum); + +#ifndef LDAP_CONTROL_PAGE_OID +#define LDAP_CONTROL_PAGE_OID "1.2.840.113556.1.4.319" +#endif /* !LDAP_CONTROL_PAGE_OID */ + +#ifndef LDAP_CONTROL_VALUESRETURNFILTER +#define LDAP_CONTROL_VALUESRETURNFILTER "1.2.826.0.1.3344810.2.3" /* RFC 3876 */ +#endif /* !LDAP_CONTROL_VALUESRETURNFILTER */ + +/* *** functions *** */ +PYLDAP_FUNC(void) LDAPinit_functions(PyObject *); + +/* *** ldapcontrol *** */ +PYLDAP_FUNC(void) LDAPinit_control(PyObject *d); +PYLDAP_FUNC(void) LDAPControl_List_DEL(LDAPControl **); +PYLDAP_FUNC(int) LDAPControls_from_object(PyObject *, LDAPControl ***); +PYLDAP_FUNC(PyObject *) LDAPControls_to_List(LDAPControl **ldcs); + +/* *** ldapobject *** */ +typedef struct { + PyObject_HEAD LDAP *ldap; + PyThreadState *_save; /* for thread saving on referrals */ + int valid; +} LDAPObject; + +PYLDAP_DATA(PyTypeObject) LDAP_Type; +PYLDAP_FUNC(LDAPObject *) newLDAPObject(LDAP *); + +/* macros to allow thread saving in the context of an LDAP connection */ + +#define LDAP_BEGIN_ALLOW_THREADS( l ) \ + { \ + LDAPObject *lo = (l); \ + if (lo->_save != NULL) \ + Py_FatalError( "saving thread twice?" ); \ + lo->_save = PyEval_SaveThread(); \ + } + +#define LDAP_END_ALLOW_THREADS( l ) \ + { \ + LDAPObject *lo = (l); \ + PyThreadState *_save = lo->_save; \ + lo->_save = NULL; \ + PyEval_RestoreThread( _save ); \ + } + +/* *** messages *** */ +PYLDAP_FUNC(PyObject *) +LDAPmessage_to_python(LDAP *ld, LDAPMessage *m, int add_ctrls, + int add_intermediates); + +/* *** options *** */ +PYLDAP_FUNC(int) LDAP_optionval_by_name(const char *name); +PYLDAP_FUNC(int) LDAP_set_option(LDAPObject *self, int option, + PyObject *value); +PYLDAP_FUNC(PyObject *) LDAP_get_option(LDAPObject *self, int option); +PYLDAP_FUNC(void) set_timeval_from_double(struct timeval *tv, double d); + +#endif /* pythonldap_h */ diff --git a/setup.py b/setup.py index 6da3f491..dbf66a04 100644 --- a/setup.py +++ b/setup.py @@ -117,15 +117,8 @@ class OpenLDAP2: 'Modules/berval.c', ], depends = [ - 'Modules/LDAPObject.h', - 'Modules/berval.h', - 'Modules/common.h', + 'Modules/pythonldap.h', 'Modules/constants_generated.h', - 'Modules/constants.h', - 'Modules/functions.h', - 'Modules/ldapcontrol.h', - 'Modules/message.h', - 'Modules/options.h', ], libraries = LDAP_CLASS.libs, include_dirs = ['Modules'] + LDAP_CLASS.include_dirs, From f48101097eb08f902daed5b8e7836c54cf44b0f4 Mon Sep 17 00:00:00 2001 From: Christian Heimes Date: Mon, 30 Oct 2023 09:40:12 +0100 Subject: [PATCH 63/73] refactor: Remove Python 2 vestiges The C code had a few version checks for Python 2. python-ldap requires Python >= 3.6. Signed-off-by: Christian Heimes --- Modules/LDAPObject.c | 51 +++++++++++--------------------------------- Modules/constants.c | 8 +++---- Modules/ldapmodule.c | 45 ++++++++++---------------------------- Modules/options.c | 2 +- Modules/pythonldap.h | 6 ------ 5 files changed, 28 insertions(+), 84 deletions(-) diff --git a/Modules/LDAPObject.c b/Modules/LDAPObject.c index eaf831bd..71fac73e 100644 --- a/Modules/LDAPObject.c +++ b/Modules/LDAPObject.c @@ -270,13 +270,8 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) if (attrlist == Py_None) { /* None means a NULL attrlist */ -#if PY_MAJOR_VERSION == 2 - } - else if (PyBytes_Check(attrlist)) { -#else } else if (PyUnicode_Check(attrlist)) { -#endif /* caught by John Benninghoff */ LDAPerror_TypeError ("attrs_from_List(): expected *list* of strings, not a string", @@ -287,11 +282,7 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) PyObject *item = NULL; Py_ssize_t i, len, strlen; -#if PY_MAJOR_VERSION >= 3 const char *str; -#else - char *str; -#endif seq = PySequence_Fast(attrlist, "expected list of strings or None"); if (seq == NULL) @@ -309,24 +300,12 @@ attrs_from_List(PyObject *attrlist, char ***attrsp) item = PySequence_Fast_GET_ITEM(seq, i); if (item == NULL) goto error; -#if PY_MAJOR_VERSION == 2 - /* Encoded in Python to UTF-8 */ - if (!PyBytes_Check(item)) { - LDAPerror_TypeError - ("attrs_from_List(): expected bytes in list", item); - goto error; - } - if (PyBytes_AsStringAndSize(item, &str, &strlen) == -1) { - goto error; - } -#else if (!PyUnicode_Check(item)) { LDAPerror_TypeError ("attrs_from_List(): expected string in list", item); goto error; } str = PyUnicode_AsUTF8AndSize(item, &strlen); -#endif /* Make a copy. PyBytes_AsString* / PyUnicode_AsUTF8* return * internal values that must be treated like const char. Python * 3.7 actually returns a const char. @@ -515,7 +494,7 @@ l_ldap_add_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_simple_bind */ @@ -566,7 +545,7 @@ l_ldap_simple_bind(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #ifdef HAVE_SASL @@ -724,7 +703,7 @@ l_ldap_sasl_bind_s(LDAPObject *self, PyObject *args) } else if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(ldaperror); + return PyLong_FromLong(ldaperror); } static PyObject * @@ -751,15 +730,9 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) * unsigned int, we need to use the "I" flag if we're running Python 2.3+ and a * "i" otherwise. */ -#if (PY_MAJOR_VERSION == 2) && (PY_MINOR_VERSION < 3) - if (!PyArg_ParseTuple - (args, "sOOOi:sasl_interactive_bind_s", &who, &SASLObject, - &serverctrls, &clientctrls, &sasl_flags)) -#else if (!PyArg_ParseTuple (args, "sOOOI:sasl_interactive_bind_s", &who, &SASLObject, &serverctrls, &clientctrls, &sasl_flags)) -#endif return NULL; if (not_valid(self)) @@ -803,7 +776,7 @@ l_ldap_sasl_interactive_bind_s(LDAPObject *self, PyObject *args) if (msgid != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #endif @@ -852,7 +825,7 @@ l_ldap_cancel(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } #endif @@ -906,7 +879,7 @@ l_ldap_compare_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_delete_ext */ @@ -952,7 +925,7 @@ l_ldap_delete_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_modify_ext */ @@ -1009,7 +982,7 @@ l_ldap_modify_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_rename */ @@ -1059,7 +1032,7 @@ l_ldap_rename(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_result4 */ @@ -1275,7 +1248,7 @@ l_ldap_search_ext(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_whoami_s (available since OpenLDAP 2.1.13) */ @@ -1445,7 +1418,7 @@ l_ldap_passwd(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* ldap_extended_operation */ @@ -1496,7 +1469,7 @@ l_ldap_extended_operation(LDAPObject *self, PyObject *args) if (ldaperror != LDAP_SUCCESS) return LDAPerror(self->ldap); - return PyInt_FromLong(msgid); + return PyLong_FromLong(msgid); } /* methods */ diff --git a/Modules/constants.c b/Modules/constants.c index b70db245..f0a0da94 100644 --- a/Modules/constants.c +++ b/Modules/constants.c @@ -105,20 +105,20 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) } if (msgtype > 0) { - pyresult = PyInt_FromLong(msgtype); + pyresult = PyLong_FromLong(msgtype); if (pyresult) PyDict_SetItemString(info, "msgtype", pyresult); Py_XDECREF(pyresult); } if (msgid >= 0) { - pyresult = PyInt_FromLong(msgid); + pyresult = PyLong_FromLong(msgid); if (pyresult) PyDict_SetItemString(info, "msgid", pyresult); Py_XDECREF(pyresult); } - pyresult = PyInt_FromLong(errnum); + pyresult = PyLong_FromLong(errnum); if (pyresult) PyDict_SetItemString(info, "result", pyresult); Py_XDECREF(pyresult); @@ -129,7 +129,7 @@ LDAPraise_for_message(LDAP *l, LDAPMessage *m) Py_XDECREF(str); if (myerrno != 0) { - pyerrno = PyInt_FromLong(myerrno); + pyerrno = PyLong_FromLong(myerrno); if (pyerrno) PyDict_SetItemString(info, "errno", pyerrno); Py_XDECREF(pyerrno); diff --git a/Modules/ldapmodule.c b/Modules/ldapmodule.c index 8562337b..cb3f58fb 100644 --- a/Modules/ldapmodule.c +++ b/Modules/ldapmodule.c @@ -2,12 +2,6 @@ #include "pythonldap.h" -#if PY_MAJOR_VERSION >= 3 -PyMODINIT_FUNC PyInit__ldap(void); -#else -PyMODINIT_FUNC init_ldap(void); -#endif - #define _STR(x) #x #define STR(x) _STR(x) @@ -28,27 +22,24 @@ static PyMethodDef methods[] = { {NULL, NULL} }; +static struct PyModuleDef ldap_moduledef = { + PyModuleDef_HEAD_INIT, + "_ldap", /* m_name */ + "", /* m_doc */ + -1, /* m_size */ + methods, /* m_methods */ +}; + /* module initialisation */ -/* Common initialization code */ -PyObject * -init_ldap_module(void) +PyMODINIT_FUNC +PyInit__ldap() { PyObject *m, *d; /* Create the module and add the functions */ -#if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef ldap_moduledef = { - PyModuleDef_HEAD_INIT, - "_ldap", /* m_name */ - "", /* m_doc */ - -1, /* m_size */ - methods, /* m_methods */ - }; m = PyModule_Create(&ldap_moduledef); -#else - m = Py_InitModule("_ldap", methods); -#endif + /* Initialize LDAP class */ if (PyType_Ready(&LDAP_Type) < 0) { Py_DECREF(m); @@ -73,17 +64,3 @@ init_ldap_module(void) return m; } - -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC -init_ldap() -{ - init_ldap_module(); -} -#else -PyMODINIT_FUNC -PyInit__ldap() -{ - return init_ldap_module(); -} -#endif diff --git a/Modules/options.c b/Modules/options.c index df55ed05..4577b075 100644 --- a/Modules/options.c +++ b/Modules/options.c @@ -368,7 +368,7 @@ LDAP_get_option(LDAPObject *self, int option) res = LDAP_int_get_option(self, option, &intval); if (res != LDAP_OPT_SUCCESS) return option_error(res, "ldap_get_option"); - return PyInt_FromLong(intval); + return PyLong_FromLong(intval); #ifdef LDAP_OPT_TCP_USER_TIMEOUT case LDAP_OPT_TCP_USER_TIMEOUT: diff --git a/Modules/pythonldap.h b/Modules/pythonldap.h index ae6a1269..7703af5e 100644 --- a/Modules/pythonldap.h +++ b/Modules/pythonldap.h @@ -17,12 +17,6 @@ #include #include -/* Py2/3 compatibility */ -#if PY_VERSION_HEX >= 0x03000000 -/* In Python 3, alias PyInt to PyLong */ -#define PyInt_FromLong PyLong_FromLong -#endif - #if LDAP_VENDOR_VERSION < 20400 #error Current python-ldap requires OpenLDAP 2.4.x #endif From 16a3a3c0175aa51ee22efda118dbaeb3881cec3c Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 17 Nov 2023 12:29:34 -0800 Subject: [PATCH 64/73] Prepare a new release --- CHANGES | 17 +++++++++++++++++ Lib/ldap/pkginfo.py | 2 +- Lib/ldapurl.py | 2 +- Lib/ldif.py | 2 +- Lib/slapdtest/__init__.py | 2 +- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 500fa1e7..0491b6ef 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,20 @@ +Released 3.4.4 2022-11-17 + +Fixes: +* Reconnect race condition in ReconnectLDAPObject is now fixed +* Socket ownership is now claimed once we've passed it to libldap +* LDAP_set_option string formats are now compatible with Python 3.12 + +Doc/ +* Security Policy was created +* Broken article links are fixed now +* Bring Conscious Language improvements + +Infrastructure: +* Add testing and document support for Python 3.10, 3.11, and 3.12 + + +---------------------------------------------------------------- Released 3.4.3 2022-09-15 This is a minor release to bring back the removed OPT_X_TLS option. diff --git a/Lib/ldap/pkginfo.py b/Lib/ldap/pkginfo.py index 026e9101..18ead66c 100644 --- a/Lib/ldap/pkginfo.py +++ b/Lib/ldap/pkginfo.py @@ -1,6 +1,6 @@ """ meta attributes for packaging which does not import any dependencies """ -__version__ = '3.4.3' +__version__ = '3.4.4' __author__ = 'python-ldap project' __license__ = 'Python style' diff --git a/Lib/ldapurl.py b/Lib/ldapurl.py index 964076d3..b4dfd890 100644 --- a/Lib/ldapurl.py +++ b/Lib/ldapurl.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' __all__ = [ # constants diff --git a/Lib/ldif.py b/Lib/ldif.py index ae1d643d..fa41321c 100644 --- a/Lib/ldif.py +++ b/Lib/ldif.py @@ -3,7 +3,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' __all__ = [ # constants diff --git a/Lib/slapdtest/__init__.py b/Lib/slapdtest/__init__.py index 7ab7d2bd..7c410180 100644 --- a/Lib/slapdtest/__init__.py +++ b/Lib/slapdtest/__init__.py @@ -4,7 +4,7 @@ See https://www.python-ldap.org/ for details. """ -__version__ = '3.4.3' +__version__ = '3.4.4' from slapdtest._slapdtest import SlapdObject, SlapdTestCase, SysLogHandler from slapdtest._slapdtest import requires_ldapi, requires_sasl, requires_tls From ac5d051ec3bc3dee700ebbe3c1ba68f54fc39bc5 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Mon, 27 Nov 2023 21:52:26 +0200 Subject: [PATCH 65/73] Update link to unofficial Windows binary builds (#524) --- Doc/installing.rst | 4 ++-- Doc/spelling_wordlist.txt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index e4518c11..6627ce5d 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -63,8 +63,8 @@ to get up to date information which versions are available. Windows ------- -Unofficial packages for Windows are available on -`Christoph Gohlke's page `_. +Unofficial binary builds for Windows are provided by Christoph Gohlke, available at +`python-ldap-build `_. `FreeBSD `_ diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index e6c2aedd..8cdd9f16 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -25,6 +25,7 @@ changeNumber changesOnly changeType changeTypes +Christoph cidict clientctrls conf @@ -56,6 +57,7 @@ filterstr filterStr formatOID func +Gohlke GPG Heimdal hostport From 10985e38902d170402acdfe6fabece5db70437cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl=20Bourgault?= Date: Thu, 22 Feb 2024 08:19:11 +0100 Subject: [PATCH 66/73] docs: add missing negation in contributing.rst (#552) Current description contains a sentence that miss a negative form, contradicting previous sentence and leaving the reader with an ambiguity. --- Doc/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index bbaab491..6ef8a5a8 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -19,7 +19,7 @@ Communication Always keep in mind that python-ldap is developed and maintained by volunteers. We're happy to share our work, and to work with you to make the library better, -but (until you pay someone), there's obligation to provide assistance. +but (until you pay someone), there's no obligation to provide assistance. So, keep it friendly, respectful, and supportive! From 06fdd3dc4d3da82afa8d445cb8f1e8842c824236 Mon Sep 17 00:00:00 2001 From: RafaelWO <38643099+RafaelWO@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:58:54 +0100 Subject: [PATCH 67/73] Update docs on installation requirements (#548) Separate building and testing requirements for Debian --- Doc/installing.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Doc/installing.rst b/Doc/installing.rst index 6627ce5d..03e7a295 100644 --- a/Doc/installing.rst +++ b/Doc/installing.rst @@ -143,10 +143,15 @@ Packages for building:: Debian ------ +Packages for building:: + + # apt-get install build-essential ldap-utils \ + libldap2-dev libsasl2-dev + Packages for building and testing:: - # apt-get install build-essential python3-dev \ - libldap2-dev libsasl2-dev slapd ldap-utils tox \ + # apt-get install build-essential ldap-utils \ + libldap2-dev libsasl2-dev slapd python3-dev tox \ lcov valgrind .. note:: From a58282adbc6b1f5f9755458227e6bb8667b72f6b Mon Sep 17 00:00:00 2001 From: Quanah Gibson-Mount Date: Mon, 22 Apr 2024 22:18:09 +0000 Subject: [PATCH 68/73] Fixes #565 - Use name values instead of raw decimal Use the name values for result types in syncrepl.py rather than the raw decimal values. Signed-off-by: Quanah Gibson-Mount --- Lib/ldap/syncrepl.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/ldap/syncrepl.py b/Lib/ldap/syncrepl.py index 1708b468..fd0c1285 100644 --- a/Lib/ldap/syncrepl.py +++ b/Lib/ldap/syncrepl.py @@ -12,6 +12,7 @@ from ldap.pkginfo import __version__, __author__, __license__ from ldap.controls import RequestControl, ResponseControl, KNOWN_RESPONSE_CONTROLS +from ldap import RES_SEARCH_RESULT, RES_SEARCH_ENTRY, RES_INTERMEDIATE __all__ = [ 'SyncreplConsumer', @@ -407,7 +408,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): all=0, ) - if type == 101: + if type == RES_SEARCH_RESULT: # search result. This marks the end of a refreshOnly session. # look for a SyncDone control, save the cookie, and if necessary # delete non-present entries. @@ -420,7 +421,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): return False - elif type == 100: + elif type == RES_SEARCH_ENTRY: # search entry with associated SyncState control for m in msg: dn, attrs, ctrls = m @@ -439,7 +440,7 @@ def syncrepl_poll(self, msgid=-1, timeout=None, all=0): self.syncrepl_set_cookie(c.cookie) break - elif type == 121: + elif type == RES_INTERMEDIATE: # Intermediate message. If it is a SyncInfoMessage, parse it for m in msg: rname, resp, ctrls = m From 8b27db3d605974fc308b3b52ec464e354dcbafa0 Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Thu, 10 Oct 2024 22:55:29 -0700 Subject: [PATCH 69/73] Add support for Python 3.13 (#576) Update GitHub Actions. Explicitly install python3-setuptools for Tox env runs on Fedora. --- .github/workflows/ci.yml | 4 +++- .github/workflows/tox-fedora.yml | 5 +++-- setup.py | 1 + tox.ini | 5 ++++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37843f31..2f835d76 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,9 @@ jobs: - "3.10" - "3.11" - "3.12" + - "3.13" - "pypy3.9" + - "pypy3.10" image: - "ubuntu-22.04" include: @@ -43,7 +45,7 @@ jobs: - name: Disable AppArmor run: sudo aa-disable /usr/sbin/slapd - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index b86303fe..4c4c18f0 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -9,7 +9,7 @@ jobs: tox_test: name: Tox env "${{matrix.tox_env}}" on Fedora steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Run Tox tests uses: fedora-python/tox-github-action@main with: @@ -17,7 +17,7 @@ jobs: dnf_install: > @c-development openldap-devel python3-devel openldap-servers openldap-clients lcov clang-analyzer valgrind - enchant + enchant python3-setuptools strategy: matrix: tox_env: @@ -28,6 +28,7 @@ jobs: - py310 - py311 - py312 + - py313 - c90-py36 - c90-py37 - py3-nosasltls diff --git a/setup.py b/setup.py index dbf66a04..8e7963a1 100644 --- a/setup.py +++ b/setup.py @@ -93,6 +93,7 @@ class OpenLDAP2: 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', # Note: when updating Python versions, also change tox.ini and .github/workflows/* 'Topic :: Database', diff --git a/tox.ini b/tox.ini index beade024..22752067 100644 --- a/tox.ini +++ b/tox.ini @@ -17,10 +17,12 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 pypy3.9: pypy3.9 + pypy3.10: pypy3.10 [testenv] -deps = +deps = setuptools passenv = WITH_GCOV # - Enable BytesWarning # - Turn all warnings into exceptions. @@ -98,6 +100,7 @@ deps = markdown sphinx sphinxcontrib-spelling + setuptools commands = {envpython} setup.py check --restructuredtext --metadata --strict {envpython} -m markdown README -f {envtmpdir}/README.html From 326f8708ca6d6fff36893a38f38b645bed9c5e6f Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 11 Oct 2024 08:16:27 -0700 Subject: [PATCH 70/73] Deprecate Pagure repo (#579) --- Doc/contributing.rst | 7 +------ Doc/spelling_wordlist.txt | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/Doc/contributing.rst b/Doc/contributing.rst index 6ef8a5a8..de63a2e3 100644 --- a/Doc/contributing.rst +++ b/Doc/contributing.rst @@ -72,9 +72,6 @@ If you're used to open-source Python development with Git, here's the gist: .. _the bug tracker: https://github.com/python-ldap/python-ldap/issues .. _tox: https://tox.readthedocs.io/en/latest/ -Or, if you prefer to avoid closed-source services: - -* ``git clone https://pagure.io/python-ldap`` * Send bug reports and patches to the mailing list. * Run tests with `tox`_; ignore Python interpreters you don't have locally. * Read the documentation directly at `Read the Docs`_. @@ -203,8 +200,6 @@ remember: * Consider making the summary line suitable for the CHANGES document, and starting it with a prefix like ``Lib:`` or ``Tests:``. -* Push to Pagure as well. - If you have good reason to break the “rules”, go ahead and break them, but mention why. @@ -224,7 +219,7 @@ If you are tasked with releasing python-ldap, remember to: * Run ``python setup.py sdist``, and smoke-test the resulting package (install in a clean virtual environment, import ``ldap``). * Create GPG-signed Git tag: ``git tag -s python-ldap-{version}``. - Push it to GitHub and Pagure. + Push it to GitHub. * Release the ``sdist`` on PyPI. * Announce the release on the mailing list. Mention the Git hash. diff --git a/Doc/spelling_wordlist.txt b/Doc/spelling_wordlist.txt index 8cdd9f16..e2150d9a 100644 --- a/Doc/spelling_wordlist.txt +++ b/Doc/spelling_wordlist.txt @@ -100,7 +100,6 @@ oc oid oids OpenLDAP -Pagure postalAddress pre previousDN From e628f1582b46269ba6c31e1e3ee8a952cb32bb7d Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Tue, 28 Jan 2025 14:26:58 -0800 Subject: [PATCH 71/73] Deprecate EOL Python 3.6, 3.7, 3.8 versions --- .github/workflows/ci.yml | 9 ++------- .github/workflows/tox-fedora.yml | 7 +------ pyproject.toml | 4 ---- setup.py | 5 +---- tox.ini | 7 ++----- 5 files changed, 6 insertions(+), 26 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f835d76..06d0b2ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ --- name: CI -on: +on: push: pull_request: schedule: @@ -20,8 +20,6 @@ jobs: fail-fast: false matrix: python-version: - - "3.7" - - "3.8" - "3.9" - "3.10" - "3.11" @@ -29,11 +27,8 @@ jobs: - "3.13" - "pypy3.9" - "pypy3.10" - image: + image: - "ubuntu-22.04" - include: - - python-version: "3.6" - image: "ubuntu-20.04" steps: - name: Checkout uses: "actions/checkout@v4" diff --git a/.github/workflows/tox-fedora.yml b/.github/workflows/tox-fedora.yml index 4c4c18f0..bc6f45c5 100644 --- a/.github/workflows/tox-fedora.yml +++ b/.github/workflows/tox-fedora.yml @@ -21,20 +21,15 @@ jobs: strategy: matrix: tox_env: - - py36 - - py37 - - py38 - py39 - py310 - py311 - py312 - py313 - - c90-py36 - - c90-py37 - py3-nosasltls - py3-trace - pypy3 - doc # Use GitHub's Linux Docker host - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 diff --git a/pyproject.toml b/pyproject.toml index dda8dbc1..75f7c06a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,3 @@ -[tool.black] -line-length = 88 -target-version = ['py36', 'py37', 'py38'] - [tool.isort] line_length=88 known_first_party=['ldap', '_ldap', 'ldapurl', 'ldif', 'slapdtest'] diff --git a/setup.py b/setup.py index 8e7963a1..ea7364cd 100644 --- a/setup.py +++ b/setup.py @@ -86,9 +86,6 @@ class OpenLDAP2: 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -160,6 +157,6 @@ class OpenLDAP2: 'pyasn1_modules >= 0.1.5', ], zip_safe=False, - python_requires='>=3.6', + python_requires='>=3.9', test_suite = 'Tests', ) diff --git a/tox.ini b/tox.ini index 22752067..0b284a4e 100644 --- a/tox.ini +++ b/tox.ini @@ -5,15 +5,12 @@ [tox] # Note: when updating Python versions, also change setup.py and .github/worlflows/* -envlist = py{36,37,38,39,310,311,312},c90-py{36,37},py3-nosasltls,doc,py3-trace,pypy3.9 +envlist = py{39,310,311,312},py3-nosasltls,doc,py3-trace,pypy3.9 minver = 1.8 [gh-actions] python = - 3.6: py36 - 3.7: py37 - 3.8: py38, doc, py3-nosasltls - 3.9: py39, py3-trace + 3.9: py39, py3-trace, doc, py3-nosasltls 3.10: py310 3.11: py311 3.12: py312 From 30b24d5673095fecc73f652cfa41efc6208f9d72 Mon Sep 17 00:00:00 2001 From: Jiayu Hu <86949267+JennyHo5@users.noreply.github.com> Date: Sun, 13 Jul 2025 01:25:03 -0400 Subject: [PATCH 72/73] Fix typo (#584) The cookie is saved with key `cookie` intead of `ldap_cookie` in the `self.__data` dict --- Demo/pyasn1/syncrepl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Demo/pyasn1/syncrepl.py b/Demo/pyasn1/syncrepl.py index f1f24e19..754b237a 100644 --- a/Demo/pyasn1/syncrepl.py +++ b/Demo/pyasn1/syncrepl.py @@ -76,7 +76,7 @@ def syncrepl_entry(self, dn, attributes, uuid): 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: + if 'cookie' in self.__data: self.perform_application_sync(dn, attributes, previous_attributes) def syncrepl_delete(self,uuids): @@ -98,7 +98,7 @@ def syncrepl_present(self,uuids,refreshDeletes=False): deletedEntries = [ uuid for uuid in self.__data.keys() - if uuid not in self.__presentUUIDs and uuid != 'ldap_cookie' + if uuid not in self.__presentUUIDs and uuid != 'cookie' ] self.syncrepl_delete( deletedEntries ) # Phase is now completed, reset the list From 2880183370f99ead12d25ac4683c958555aa1f8b Mon Sep 17 00:00:00 2001 From: Florian Best Date: Fri, 1 Aug 2025 01:57:17 +0200 Subject: [PATCH 73/73] docs(ldapobject): fix typos in docstring (#590) --- Doc/reference/ldap.rst | 2 +- Lib/ldap/ldapobject.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/reference/ldap.rst b/Doc/reference/ldap.rst index d059dfa4..4911b7c7 100644 --- a/Doc/reference/ldap.rst +++ b/Doc/reference/ldap.rst @@ -1364,7 +1364,7 @@ and wait for and return with the server's result, or with This synchronous method implements the LDAP "Who Am I?" extended operation. - It is useful for finding out to find out which identity + It is useful for finding out which identity is assumed by the LDAP server after a SASL bind. .. seealso:: diff --git a/Lib/ldap/ldapobject.py b/Lib/ldap/ldapobject.py index 7a9c17f6..290d92b3 100644 --- a/Lib/ldap/ldapobject.py +++ b/Lib/ldap/ldapobject.py @@ -521,7 +521,7 @@ def result(self,msgid=ldap.RES_ANY,all=1,timeout=None): The method returns a tuple of the form (result_type, result_data). The result_type is one of the constants RES_*. - See search() for a description of the search result's + See search_ext() for a description of the search result's result_data, otherwise the result_data is normally meaningless. The result() method will block for timeout seconds, or @@ -588,7 +588,7 @@ def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverct values are stored in a list as dictionary value. The DN in dn is extracted using the underlying ldap_get_dn(), - which may raise an exception of the DN is malformed. + which may raise an exception if the DN is malformed. If attrsonly is non-zero, the values of attrs will be meaningless (they are not transmitted in the result). pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy