|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267 |
- """
- ldapobject.py - wraps class _ldap.LDAPObject
-
- See https://www.python-ldap.org/ for details.
- """
-
- from __future__ import unicode_literals
-
- from os import strerror
-
- from ldap.pkginfo import __version__, __author__, __license__
-
- __all__ = [
- 'LDAPObject',
- 'SimpleLDAPObject',
- 'ReconnectLDAPObject',
- 'LDAPBytesWarning'
- ]
-
-
- if __debug__:
- # Tracing is only supported in debugging mode
- import traceback
-
- import sys,time,pprint,_ldap,ldap,ldap.sasl,ldap.functions
- import warnings
-
- from ldap.schema import SCHEMA_ATTRS
- from ldap.controls import LDAPControl,DecodeControlTuples,RequestControlTuples
- from ldap.extop import ExtendedRequest,ExtendedResponse
- from ldap.compat import reraise
-
- from ldap import LDAPError
-
- PY2 = sys.version_info[0] <= 2
- if PY2:
- text_type = unicode
- else:
- text_type = str
-
-
- # See SimpleLDAPObject._bytesify_input
- _LDAP_WARN_SKIP_FRAME = True
-
- class LDAPBytesWarning(BytesWarning):
- """python-ldap bytes mode warning
- """
-
- def _raise_byteswarning(message):
- """Raise LDAPBytesWarning
- """
-
- # Call stacks that raise the warning tend to be complicated, so
- # getting a useful stacklevel is tricky.
- # We walk stack frames, ignoring functions in uninteresting files,
- # based on the _LDAP_WARN_SKIP_FRAME marker in globals().
- stacklevel = 2
- try:
- getframe = sys._getframe
- except AttributeError:
- pass
- else:
- frame = sys._getframe(stacklevel)
- while frame and frame.f_globals.get('_LDAP_WARN_SKIP_FRAME'):
- stacklevel += 1
- frame = frame.f_back
- warnings.warn(message, LDAPBytesWarning, stacklevel=stacklevel+1)
-
-
- class NO_UNIQUE_ENTRY(ldap.NO_SUCH_OBJECT):
- """
- Exception raised if a LDAP search returned more than entry entry
- although assumed to return a unique single search result.
- """
-
-
- class SimpleLDAPObject:
- """
- Drop-in wrapper class around _ldap.LDAPObject
- """
-
- CLASSATTR_OPTION_MAPPING = {
- "protocol_version": ldap.OPT_PROTOCOL_VERSION,
- "deref": ldap.OPT_DEREF,
- "referrals": ldap.OPT_REFERRALS,
- "timelimit": ldap.OPT_TIMELIMIT,
- "sizelimit": ldap.OPT_SIZELIMIT,
- "network_timeout": ldap.OPT_NETWORK_TIMEOUT,
- "error_number":ldap.OPT_ERROR_NUMBER,
- "error_string":ldap.OPT_ERROR_STRING,
- "matched_dn":ldap.OPT_MATCHED_DN,
- }
-
- def __init__(
- self,uri,
- trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
- bytes_strictness=None,
- ):
- self._trace_level = trace_level or ldap._trace_level
- self._trace_file = trace_file or ldap._trace_file
- self._trace_stack_limit = trace_stack_limit
- self._uri = uri
- self._ldap_object_lock = self._ldap_lock('opcall')
- self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri)
- self.timeout = -1
- self.protocol_version = ldap.VERSION3
-
- # Bytes mode
- # ----------
-
- if PY2:
- if bytes_mode is None:
- bytes_mode = True
- if bytes_strictness is None:
- _raise_byteswarning(
- "Under Python 2, python-ldap uses bytes by default. "
- "This will be removed in Python 3 (no bytes for "
- "DN/RDN/field names). "
- "Please call initialize(..., bytes_mode=False) explicitly.")
- bytes_strictness = 'warn'
- else:
- if bytes_strictness is None:
- bytes_strictness = 'error'
- else:
- if bytes_mode:
- raise ValueError("bytes_mode is *not* supported under Python 3.")
- bytes_mode = False
- bytes_strictness = 'error'
- self.bytes_mode = bytes_mode
- self.bytes_strictness = bytes_strictness
-
- def _bytesify_input(self, arg_name, value):
- """Adapt a value following bytes_mode in Python 2.
-
- In Python 3, returns the original value unmodified.
-
- With bytes_mode ON, takes bytes or None and returns bytes or None.
- With bytes_mode OFF, takes unicode or None and returns bytes or None.
-
- For the wrong argument type (unicode or bytes, respectively),
- behavior depends on the bytes_strictness setting.
- In all cases, bytes or None are returned (or an exception is raised).
- """
- if not PY2:
- return value
- if value is None:
- return value
-
- elif self.bytes_mode:
- if isinstance(value, bytes):
- return value
- elif self.bytes_strictness == 'silent':
- pass
- elif self.bytes_strictness == 'warn':
- _raise_byteswarning(
- "Received non-bytes value for '{}' in bytes mode; "
- "please choose an explicit "
- "option for bytes_mode on your LDAP connection".format(arg_name))
- else:
- raise TypeError(
- "All provided fields *must* be bytes when bytes mode is on; "
- "got type '{}' for '{}'.".format(type(value).__name__, arg_name)
- )
- return value.encode('utf-8')
- else:
- if isinstance(value, unicode):
- return value.encode('utf-8')
- elif self.bytes_strictness == 'silent':
- pass
- elif self.bytes_strictness == 'warn':
- _raise_byteswarning(
- "Received non-text value for '{}' with bytes_mode off and "
- "bytes_strictness='warn'".format(arg_name))
- else:
- raise TypeError(
- "All provided fields *must* be text when bytes mode is off; "
- "got type '{}' for '{}'.".format(type(value).__name__, arg_name)
- )
- return value
-
- def _bytesify_modlist(self, arg_name, modlist, with_opcode):
- """Adapt a modlist according to bytes_mode.
-
- A modlist is a tuple of (op, attr, value), where:
- - With bytes_mode ON, attr is checked to be bytes
- - With bytes_mode OFF, attr is converted from unicode to bytes
- - value is *always* bytes
- """
- if not PY2:
- return modlist
- if with_opcode:
- return tuple(
- (op, self._bytesify_input(arg_name, attr), val)
- for op, attr, val in modlist
- )
- else:
- return tuple(
- (self._bytesify_input(arg_name, attr), val)
- for attr, val in modlist
- )
-
- def _unbytesify_text_value(self, value):
- """Adapt a 'known text, UTF-8 encoded' returned value following bytes_mode.
-
- With bytes_mode ON, takes bytes or None and returns bytes or None.
- With bytes_mode OFF, takes bytes or None and returns unicode or None.
-
- This function should only be applied on field *values*; distinguished names
- or field *names* are already natively handled in result4.
- """
- if value is None:
- return value
-
- # Preserve logic of assertions only under Python 2
- if PY2:
- assert isinstance(value, bytes), "Expected bytes value, got text instead (%r)" % (value,)
-
- if self.bytes_mode:
- return value
- else:
- return value.decode('utf-8')
-
- def _maybe_rebytesify_text(self, value):
- """Re-encodes text to bytes if needed by bytes_mode.
-
- Takes unicode (and checks for it), and returns:
- - bytes under bytes_mode
- - unicode otherwise.
- """
- if not PY2:
- return value
-
- if value is None:
- return value
-
- assert isinstance(value, text_type), "Should return text, got bytes instead (%r)" % (value,)
- if not self.bytes_mode:
- return value
- else:
- return value.encode('utf-8')
-
- def _bytesify_result_value(self, result_value):
- """Applies bytes_mode to a result value.
-
- Such a value can either be:
- - a dict mapping an attribute name to its list of values
- (where attribute names are unicode and values bytes)
- - a list of referals (which are unicode)
- """
- if not PY2:
- return result_value
- if hasattr(result_value, 'items'):
- # It's a attribute_name: [values] dict
- return {
- self._maybe_rebytesify_text(key): value
- for (key, value) in result_value.items()
- }
- elif isinstance(result_value, bytes):
- return result_value
- else:
- # It's a list of referals
- # Example value:
- # [u'ldap://DomainDnsZones.xxxx.root.local/DC=DomainDnsZones,DC=xxxx,DC=root,DC=local']
- return [self._maybe_rebytesify_text(referal) for referal in result_value]
-
- def _bytesify_results(self, results, with_ctrls=False):
- """Converts a "results" object according to bytes_mode.
-
- Takes:
- - a list of (dn, {field: [values]}) if with_ctrls is False
- - a list of (dn, {field: [values]}, ctrls) if with_ctrls is True
-
- And, if bytes_mode is on, converts dn and fields to bytes.
- """
- if not PY2:
- return results
- if with_ctrls:
- return [
- (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields), ctrls)
- for (dn, fields, ctrls) in results
- ]
- else:
- return [
- (self._maybe_rebytesify_text(dn), self._bytesify_result_value(fields))
- for (dn, fields) in results
- ]
-
- def _ldap_lock(self,desc=''):
- if ldap.LIBLDAP_R:
- return ldap.LDAPLock(desc='%s within %s' %(desc,repr(self)))
- else:
- return ldap._ldap_module_lock
-
- def _ldap_call(self,func,*args,**kwargs):
- """
- Wrapper method mainly for serializing calls into OpenLDAP libs
- and trace logs
- """
- self._ldap_object_lock.acquire()
- if __debug__:
- if self._trace_level>=1:
- self._trace_file.write('*** %s %s - %s\n%s\n' % (
- repr(self),
- self._uri,
- '.'.join((self.__class__.__name__,func.__name__)),
- pprint.pformat((args,kwargs))
- ))
- if self._trace_level>=9:
- traceback.print_stack(limit=self._trace_stack_limit,file=self._trace_file)
- diagnostic_message_success = None
- try:
- try:
- result = func(*args,**kwargs)
- if __debug__ and self._trace_level>=2:
- if func.__name__!="unbind_ext":
- diagnostic_message_success = self._l.get_option(ldap.OPT_DIAGNOSTIC_MESSAGE)
- finally:
- self._ldap_object_lock.release()
- except LDAPError as e:
- exc_type,exc_value,exc_traceback = sys.exc_info()
- try:
- if 'info' not in e.args[0] and 'errno' in e.args[0]:
- e.args[0]['info'] = strerror(e.args[0]['errno'])
- except IndexError:
- pass
- if __debug__ and self._trace_level>=2:
- self._trace_file.write('=> LDAPError - %s: %s\n' % (e.__class__.__name__,str(e)))
- try:
- reraise(exc_type, exc_value, exc_traceback)
- finally:
- exc_type = exc_value = exc_traceback = None
- else:
- if __debug__ and self._trace_level>=2:
- if not diagnostic_message_success is None:
- self._trace_file.write('=> diagnosticMessage: %s\n' % (repr(diagnostic_message_success)))
- self._trace_file.write('=> result:\n%s\n' % (pprint.pformat(result)))
- return result
-
- def __setattr__(self,name,value):
- if name in self.CLASSATTR_OPTION_MAPPING:
- self.set_option(self.CLASSATTR_OPTION_MAPPING[name],value)
- else:
- self.__dict__[name] = value
-
- def __getattr__(self,name):
- if name in self.CLASSATTR_OPTION_MAPPING:
- return self.get_option(self.CLASSATTR_OPTION_MAPPING[name])
- elif name in self.__dict__:
- return self.__dict__[name]
- else:
- raise AttributeError('%s has no attribute %s' % (
- self.__class__.__name__,repr(name)
- ))
-
- def fileno(self):
- """
- Returns file description of LDAP connection.
-
- Just a convenience wrapper for LDAPObject.get_option(ldap.OPT_DESC)
- """
- return self.get_option(ldap.OPT_DESC)
-
- def abandon_ext(self,msgid,serverctrls=None,clientctrls=None):
- """
- abandon_ext(msgid[,serverctrls=None[,clientctrls=None]]) -> None
- abandon(msgid) -> None
- Abandons or cancels an LDAP operation in progress. The msgid should
- be the message id of an outstanding LDAP operation as returned
- by the asynchronous methods search(), modify() etc. The caller
- can expect that the result of an abandoned operation will not be
- returned from a future call to result().
- """
- return self._ldap_call(self._l.abandon_ext,msgid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def abandon(self,msgid):
- return self.abandon_ext(msgid,None,None)
-
- def cancel(self,cancelid,serverctrls=None,clientctrls=None):
- """
- cancel(cancelid[,serverctrls=None[,clientctrls=None]]) -> int
- Send cancels extended operation for an LDAP operation specified by cancelid.
- The cancelid should be the message id of an outstanding LDAP operation as returned
- by the asynchronous methods search(), modify() etc. The caller
- can expect that the result of an abandoned operation will not be
- returned from a future call to result().
- In opposite to abandon() this extended operation gets an result from
- the server and thus should be preferred if the server supports it.
- """
- return self._ldap_call(self._l.cancel,cancelid,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def cancel_s(self,cancelid,serverctrls=None,clientctrls=None):
- msgid = self.cancel(cancelid,serverctrls,clientctrls)
- try:
- res = self.result(msgid,all=1,timeout=self.timeout)
- except (ldap.CANCELLED,ldap.SUCCESS):
- res = None
- return res
-
- def add_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
- """
- add_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
- This function adds a new entry with a distinguished name
- specified by dn which means it must not already exist.
- The parameter modlist is similar to the one passed to modify(),
- except that no operation integer need be included in the tuples.
- """
- if PY2:
- dn = self._bytesify_input('dn', dn)
- modlist = self._bytesify_modlist('modlist', modlist, with_opcode=False)
- return self._ldap_call(self._l.add_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def add_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
- msgid = self.add_ext(dn,modlist,serverctrls,clientctrls)
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
- return resp_type, resp_data, resp_msgid, resp_ctrls
-
- def add(self,dn,modlist):
- """
- add(dn, modlist) -> int
- This function adds a new entry with a distinguished name
- specified by dn which means it must not already exist.
- The parameter modlist is similar to the one passed to modify(),
- except that no operation integer need be included in the tuples.
- """
- return self.add_ext(dn,modlist,None,None)
-
- def add_s(self,dn,modlist):
- return self.add_ext_s(dn,modlist,None,None)
-
- def simple_bind(self,who=None,cred=None,serverctrls=None,clientctrls=None):
- """
- simple_bind([who='' [,cred='']]) -> int
- """
- if PY2:
- who = self._bytesify_input('who', who)
- cred = self._bytesify_input('cred', cred)
- return self._ldap_call(self._l.simple_bind,who,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def simple_bind_s(self,who=None,cred=None,serverctrls=None,clientctrls=None):
- """
- simple_bind_s([who='' [,cred='']]) -> 4-tuple
- """
- msgid = self.simple_bind(who,cred,serverctrls,clientctrls)
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
- return resp_type, resp_data, resp_msgid, resp_ctrls
-
- def bind(self,who,cred,method=ldap.AUTH_SIMPLE):
- """
- bind(who, cred, method) -> int
- """
- assert method==ldap.AUTH_SIMPLE,'Only simple bind supported in LDAPObject.bind()'
- return self.simple_bind(who,cred)
-
- def bind_s(self,who,cred,method=ldap.AUTH_SIMPLE):
- """
- bind_s(who, cred, method) -> None
- """
- msgid = self.bind(who,cred,method)
- return self.result(msgid,all=1,timeout=self.timeout)
-
- def sasl_interactive_bind_s(self,who,auth,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET):
- """
- sasl_interactive_bind_s(who, auth [,serverctrls=None[,clientctrls=None[,sasl_flags=ldap.SASL_QUIET]]]) -> None
- """
- return self._ldap_call(self._l.sasl_interactive_bind_s,who,auth,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls),sasl_flags)
-
- def sasl_non_interactive_bind_s(self,sasl_mech,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
- """
- Send a SASL bind request using a non-interactive SASL method (e.g. GSSAPI, EXTERNAL)
- """
- auth = ldap.sasl.sasl(
- {ldap.sasl.CB_USER:authz_id},
- sasl_mech
- )
- self.sasl_interactive_bind_s('',auth,serverctrls,clientctrls,sasl_flags)
-
- def sasl_external_bind_s(self,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
- """
- Send SASL bind request using SASL mech EXTERNAL
- """
- self.sasl_non_interactive_bind_s('EXTERNAL',serverctrls,clientctrls,sasl_flags,authz_id)
-
- def sasl_gssapi_bind_s(self,serverctrls=None,clientctrls=None,sasl_flags=ldap.SASL_QUIET,authz_id=''):
- """
- Send SASL bind request using SASL mech GSSAPI
- """
- self.sasl_non_interactive_bind_s('GSSAPI',serverctrls,clientctrls,sasl_flags,authz_id)
-
- def sasl_bind_s(self,dn,mechanism,cred,serverctrls=None,clientctrls=None):
- """
- sasl_bind_s(dn, mechanism, cred [,serverctrls=None[,clientctrls=None]]) -> int|str
- """
- return self._ldap_call(self._l.sasl_bind_s,dn,mechanism,cred,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def compare_ext(self,dn,attr,value,serverctrls=None,clientctrls=None):
- """
- compare_ext(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> int
- compare_ext_s(dn, attr, value [,serverctrls=None[,clientctrls=None]]) -> bool
- compare(dn, attr, value) -> int
- compare_s(dn, attr, value) -> bool
- Perform an LDAP comparison between the attribute named attr of entry
- dn, and the value value. The synchronous form returns True or False.
- The asynchronous form returns the message id of the initiates request,
- and the result of the asynchronous compare can be obtained using
- result().
-
- Note that this latter technique yields the answer by raising
- the exception objects COMPARE_TRUE or COMPARE_FALSE.
-
- A design bug in the library prevents value from containing
- nul characters.
- """
- if PY2:
- dn = self._bytesify_input('dn', dn)
- attr = self._bytesify_input('attr', attr)
- return self._ldap_call(self._l.compare_ext,dn,attr,value,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def compare_ext_s(self,dn,attr,value,serverctrls=None,clientctrls=None):
- msgid = self.compare_ext(dn,attr,value,serverctrls,clientctrls)
- try:
- ldap_res = self.result3(msgid,all=1,timeout=self.timeout)
- except ldap.COMPARE_TRUE:
- return True
- except ldap.COMPARE_FALSE:
- return False
- raise ldap.PROTOCOL_ERROR(
- 'Compare operation returned wrong result: %r' % (ldap_res)
- )
-
- def compare(self,dn,attr,value):
- return self.compare_ext(dn,attr,value,None,None)
-
- def compare_s(self,dn,attr,value):
- return self.compare_ext_s(dn,attr,value,None,None)
-
- def delete_ext(self,dn,serverctrls=None,clientctrls=None):
- """
- delete(dn) -> int
- delete_s(dn) -> None
- delete_ext(dn[,serverctrls=None[,clientctrls=None]]) -> int
- delete_ext_s(dn[,serverctrls=None[,clientctrls=None]]) -> tuple
- Performs an LDAP delete operation on dn. The asynchronous
- form returns the message id of the initiated request, and the
- result can be obtained from a subsequent call to result().
- """
- dn = self._bytesify_input('dn', dn)
- return self._ldap_call(self._l.delete_ext,dn,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def delete_ext_s(self,dn,serverctrls=None,clientctrls=None):
- msgid = self.delete_ext(dn,serverctrls,clientctrls)
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
- return resp_type, resp_data, resp_msgid, resp_ctrls
-
- def delete(self,dn):
- return self.delete_ext(dn,None,None)
-
- def delete_s(self,dn):
- return self.delete_ext_s(dn,None,None)
-
- def extop(self,extreq,serverctrls=None,clientctrls=None):
- """
- extop(extreq[,serverctrls=None[,clientctrls=None]]]) -> int
- extop_s(extreq[,serverctrls=None[,clientctrls=None[,extop_resp_class=None]]]]) ->
- (respoid,respvalue)
- Performs an LDAP extended operation. The asynchronous
- form returns the message id of the initiated request, and the
- result can be obtained from a subsequent call to extop_result().
- The extreq is an instance of class ldap.extop.ExtendedRequest.
-
- If argument extop_resp_class is set to a sub-class of
- ldap.extop.ExtendedResponse this class is used to return an
- object of this class instead of a raw BER value in respvalue.
- """
- return self._ldap_call(self._l.extop,extreq.requestName,extreq.encodedRequestValue(),RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def extop_result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
- resulttype,msg,msgid,respctrls,respoid,respvalue = self.result4(msgid,all=1,timeout=self.timeout,add_ctrls=1,add_intermediates=1,add_extop=1)
- return (respoid,respvalue)
-
- def extop_s(self,extreq,serverctrls=None,clientctrls=None,extop_resp_class=None):
- msgid = self.extop(extreq,serverctrls,clientctrls)
- res = self.extop_result(msgid,all=1,timeout=self.timeout)
- 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))
- return extop_resp_class(extop_resp_class.responseName,respvalue)
- else:
- return res
-
- def modify_ext(self,dn,modlist,serverctrls=None,clientctrls=None):
- """
- modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
- """
- if PY2:
- dn = self._bytesify_input('dn', dn)
- modlist = self._bytesify_modlist('modlist', modlist, with_opcode=True)
- return self._ldap_call(self._l.modify_ext,dn,modlist,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def modify_ext_s(self,dn,modlist,serverctrls=None,clientctrls=None):
- msgid = self.modify_ext(dn,modlist,serverctrls,clientctrls)
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
- return resp_type, resp_data, resp_msgid, resp_ctrls
-
- def modify(self,dn,modlist):
- """
- modify(dn, modlist) -> int
- modify_s(dn, modlist) -> None
- modify_ext(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> int
- modify_ext_s(dn, modlist[,serverctrls=None[,clientctrls=None]]) -> tuple
- Performs an LDAP modify operation on an entry's attributes.
- dn is the DN of the entry to modify, and modlist is the list
- of modifications to make to the entry.
-
- Each element of the list modlist should be a tuple of the form
- (mod_op,mod_type,mod_vals), where mod_op is the operation (one of
- MOD_ADD, MOD_DELETE, MOD_INCREMENT or MOD_REPLACE), mod_type is a
- string indicating the attribute type name, and mod_vals is either a
- string value or a list of string values to add, delete, increment by or
- replace respectively. For the delete operation, mod_vals may be None
- indicating that all attributes are to be deleted.
-
- The asynchronous modify() returns the message id of the
- initiated request.
- """
- return self.modify_ext(dn,modlist,None,None)
-
- def modify_s(self,dn,modlist):
- return self.modify_ext_s(dn,modlist,None,None)
-
- def modrdn(self,dn,newrdn,delold=1):
- """
- modrdn(dn, newrdn [,delold=1]) -> int
- modrdn_s(dn, newrdn [,delold=1]) -> None
- Perform a modify RDN operation. These routines take dn, the
- DN of the entry whose RDN is to be changed, and newrdn, the
- new RDN to give to the entry. The optional parameter delold
- is used to specify whether the old RDN should be kept as
- an attribute of the entry or not. The asynchronous version
- returns the initiated message id.
-
- This operation is emulated by rename() and rename_s() methods
- since the modrdn2* routines in the C library are deprecated.
- """
- return self.rename(dn,newrdn,None,delold)
-
- def modrdn_s(self,dn,newrdn,delold=1):
- return self.rename_s(dn,newrdn,None,delold)
-
- def passwd(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
- if PY2:
- user = self._bytesify_input('user', user)
- oldpw = self._bytesify_input('oldpw', oldpw)
- newpw = self._bytesify_input('newpw', newpw)
- return self._ldap_call(self._l.passwd,user,oldpw,newpw,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def passwd_s(self,user,oldpw,newpw,serverctrls=None,clientctrls=None):
- msgid = self.passwd(user,oldpw,newpw,serverctrls,clientctrls)
- return self.extop_result(msgid,all=1,timeout=self.timeout)
-
- def rename(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
- """
- rename(dn, newrdn [, newsuperior=None [,delold=1][,serverctrls=None[,clientctrls=None]]]) -> int
- rename_s(dn, newrdn [, newsuperior=None] [,delold=1][,serverctrls=None[,clientctrls=None]]) -> None
- Perform a rename entry operation. These routines take dn, the
- DN of the entry whose RDN is to be changed, newrdn, the
- new RDN, and newsuperior, the new parent DN, to give to the entry.
- If newsuperior is None then only the RDN is modified.
- The optional parameter delold is used to specify whether the
- old RDN should be kept as an attribute of the entry or not.
- The asynchronous version returns the initiated message id.
-
- This actually corresponds to the rename* routines in the
- LDAP-EXT C API library.
- """
- if PY2:
- dn = self._bytesify_input('dn', dn)
- newrdn = self._bytesify_input('newrdn', newrdn)
- newsuperior = self._bytesify_input('newsuperior', newsuperior)
- return self._ldap_call(self._l.rename,dn,newrdn,newsuperior,delold,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
-
- def rename_s(self,dn,newrdn,newsuperior=None,delold=1,serverctrls=None,clientctrls=None):
- msgid = self.rename(dn,newrdn,newsuperior,delold,serverctrls,clientctrls)
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all=1,timeout=self.timeout)
- return resp_type, resp_data, resp_msgid, resp_ctrls
-
- def result(self,msgid=ldap.RES_ANY,all=1,timeout=None):
- """
- result([msgid=RES_ANY [,all=1 [,timeout=None]]]) -> (result_type, result_data)
-
- This method is used to wait for and return the result of an
- operation previously initiated by one of the LDAP asynchronous
- operation routines (e.g. search(), modify(), etc.) They all
- returned an invocation identifier (a message id) upon successful
- initiation of their operation. This id is guaranteed to be
- unique across an LDAP session, and can be used to request the
- result of a specific operation via the msgid parameter of the
- result() method.
-
- If the result of a specific operation is required, msgid should
- be set to the invocation message id returned when the operation
- was initiated; otherwise RES_ANY should be supplied.
-
- The all parameter only has meaning for search() responses
- and is used to select whether a single entry of the search
- response should be returned, or to wait for all the results
- of the search before returning.
-
- A search response is made up of zero or more search entries
- followed by a search result. If all is 0, search entries will
- be returned one at a time as they come in, via separate calls
- to result(). If all is 1, the search response will be returned
- in its entirety, i.e. after all entries and the final search
- result have been received.
-
- For all set to 0, result tuples
- trickle in (with the same message id), and with the result type
- RES_SEARCH_ENTRY, until the final result which has a result
- type of RES_SEARCH_RESULT and a (usually) empty data field.
- When all is set to 1, only one result is returned, with a
- result type of RES_SEARCH_RESULT, and all the result tuples
- listed in the data field.
-
- 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
- result_data, otherwise the result_data is normally meaningless.
-
- The result() method will block for timeout seconds, or
- indefinitely if timeout is negative. A timeout of 0 will effect
- a poll. The timeout can be expressed as a floating-point value.
- If timeout is None the default in self.timeout is used.
-
- If a timeout occurs, a TIMEOUT exception is raised, unless
- polling (timeout = 0), in which case (None, None) is returned.
- """
- resp_type, resp_data, resp_msgid = self.result2(msgid,all,timeout)
- return resp_type, resp_data
-
- def result2(self,msgid=ldap.RES_ANY,all=1,timeout=None):
- resp_type, resp_data, resp_msgid, resp_ctrls = self.result3(msgid,all,timeout)
- return resp_type, resp_data, resp_msgid
-
- def result3(self,msgid=ldap.RES_ANY,all=1,timeout=None,resp_ctrl_classes=None):
- resp_type, resp_data, resp_msgid, decoded_resp_ctrls, retoid, retval = self.result4(
- msgid,all,timeout,
- add_ctrls=0,add_intermediates=0,add_extop=0,
- resp_ctrl_classes=resp_ctrl_classes
- )
- return resp_type, resp_data, resp_msgid, decoded_resp_ctrls
-
- def result4(self,msgid=ldap.RES_ANY,all=1,timeout=None,add_ctrls=0,add_intermediates=0,add_extop=0,resp_ctrl_classes=None):
- if timeout is None:
- timeout = self.timeout
- ldap_result = self._ldap_call(self._l.result4,msgid,all,timeout,add_ctrls,add_intermediates,add_extop)
- if ldap_result is None:
- resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = (None,None,None,None,None,None)
- else:
- if len(ldap_result)==4:
- resp_type, resp_data, resp_msgid, resp_ctrls = ldap_result
- resp_name, resp_value = None,None
- else:
- resp_type, resp_data, resp_msgid, resp_ctrls, resp_name, resp_value = ldap_result
- if add_ctrls:
- resp_data = [ (t,r,DecodeControlTuples(c,resp_ctrl_classes)) for t,r,c in resp_data ]
- decoded_resp_ctrls = DecodeControlTuples(resp_ctrls,resp_ctrl_classes)
- if resp_data is not None:
- resp_data = self._bytesify_results(resp_data, with_ctrls=add_ctrls)
- return resp_type, resp_data, resp_msgid, decoded_resp_ctrls, resp_name, resp_value
-
- def search_ext(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
- """
- search(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]]) -> int
- search_s(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0]]])
- search_st(base, scope [,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,timeout=-1]]]])
- search_ext(base,scope,[,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,serverctrls=None [,clientctrls=None [,timeout=-1 [,sizelimit=0]]]]]]])
- search_ext_s(base,scope,[,filterstr='(objectClass=*)' [,attrlist=None [,attrsonly=0 [,serverctrls=None [,clientctrls=None [,timeout=-1 [,sizelimit=0]]]]]]])
-
- Perform an LDAP search operation, with base as the DN of
- the entry at which to start the search, scope being one of
- SCOPE_BASE (to search the object itself), SCOPE_ONELEVEL
- (to search the object's immediate children), or SCOPE_SUBTREE
- (to search the object and all its descendants).
-
- filter is a string representation of the filter to
- apply in the search (see RFC 4515).
-
- Each result tuple is of the form (dn,entry), where dn is a
- string containing the DN (distinguished name) of the entry, and
- entry is a dictionary containing the attributes.
- Attributes types are used as string dictionary keys and attribute
- 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.
-
- If attrsonly is non-zero, the values of attrs will be
- meaningless (they are not transmitted in the result).
-
- The retrieved attributes can be limited with the attrlist
- parameter. If attrlist is None, all the attributes of each
- entry are returned.
-
- serverctrls=None
-
- clientctrls=None
-
- The synchronous form with timeout, search_st() or search_ext_s(),
- will block for at most timeout seconds (or indefinitely if
- timeout is negative). A TIMEOUT exception is raised if no result is
- received within the time.
-
- The amount of search results retrieved can be limited with the
- sizelimit parameter if non-zero.
- """
-
- if PY2:
- base = self._bytesify_input('base', base)
- if filterstr is None:
- # workaround for default argument,
- # see https://github.com/python-ldap/python-ldap/issues/147
- if self.bytes_mode:
- filterstr = b'(objectClass=*)'
- else:
- filterstr = u'(objectClass=*)'
- else:
- filterstr = self._bytesify_input('filterstr', filterstr)
- if attrlist is not None:
- attrlist = tuple(self._bytesify_input('attrlist', a)
- for a in attrlist)
- else:
- if filterstr is None:
- filterstr = '(objectClass=*)'
- return self._ldap_call(
- self._l.search_ext,
- base,scope,filterstr,
- attrlist,attrsonly,
- RequestControlTuples(serverctrls),
- RequestControlTuples(clientctrls),
- timeout,sizelimit,
- )
-
- def search_ext_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1,sizelimit=0):
- msgid = self.search_ext(base,scope,filterstr,attrlist,attrsonly,serverctrls,clientctrls,timeout,sizelimit)
- return self.result(msgid,all=1,timeout=timeout)[1]
-
- def search(self,base,scope,filterstr=None,attrlist=None,attrsonly=0):
- return self.search_ext(base,scope,filterstr,attrlist,attrsonly,None,None)
-
- def search_s(self,base,scope,filterstr=None,attrlist=None,attrsonly=0):
- return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout=self.timeout)
-
- def search_st(self,base,scope,filterstr=None,attrlist=None,attrsonly=0,timeout=-1):
- return self.search_ext_s(base,scope,filterstr,attrlist,attrsonly,None,None,timeout)
-
- def start_tls_s(self):
- """
- start_tls_s() -> None
- Negotiate TLS with server. The `version' attribute must have been
- set to VERSION3 before calling start_tls_s.
- If TLS could not be started an exception will be raised.
- """
- return self._ldap_call(self._l.start_tls_s)
-
- def unbind_ext(self,serverctrls=None,clientctrls=None):
- """
- unbind() -> int
- unbind_s() -> None
- unbind_ext() -> int
- unbind_ext_s() -> None
- This call is used to unbind from the directory, terminate
- the current association, and free resources. Once called, the
- connection to the LDAP server is closed and the LDAP object
- is invalid. Further invocation of methods on the object will
- yield an exception.
-
- The unbind and unbind_s methods are identical, and are
- synchronous in nature
- """
- res = self._ldap_call(self._l.unbind_ext,RequestControlTuples(serverctrls),RequestControlTuples(clientctrls))
- try:
- del self._l
- except AttributeError:
- pass
- return res
-
- def unbind_ext_s(self,serverctrls=None,clientctrls=None):
- msgid = self.unbind_ext(serverctrls,clientctrls)
- if msgid!=None:
- result = self.result3(msgid,all=1,timeout=self.timeout)
- else:
- result = None
- if __debug__ and self._trace_level>=1:
- try:
- self._trace_file.flush()
- except AttributeError:
- pass
- return result
-
- def unbind(self):
- return self.unbind_ext(None,None)
-
- def unbind_s(self):
- return self.unbind_ext_s(None,None)
-
- def whoami_s(self,serverctrls=None,clientctrls=None):
- return self._ldap_call(self._l.whoami_s,serverctrls,clientctrls)
-
- def get_option(self,option):
- result = self._ldap_call(self._l.get_option,option)
- if option==ldap.OPT_SERVER_CONTROLS or option==ldap.OPT_CLIENT_CONTROLS:
- result = DecodeControlTuples(result)
- return result
-
- def set_option(self,option,invalue):
- if option==ldap.OPT_SERVER_CONTROLS or option==ldap.OPT_CLIENT_CONTROLS:
- invalue = RequestControlTuples(invalue)
- return self._ldap_call(self._l.set_option,option,invalue)
-
- def search_subschemasubentry_s(self,dn=None):
- """
- Returns the distinguished name of the sub schema sub entry
- for a part of a DIT specified by dn.
-
- None as result indicates that the DN of the sub schema sub entry could
- not be determined.
-
- Returns: None or text/bytes depending on bytes_mode.
- """
- if self.bytes_mode:
- empty_dn = b''
- attrname = b'subschemaSubentry'
- else:
- empty_dn = u''
- attrname = u'subschemaSubentry'
- if dn is None:
- dn = empty_dn
- try:
- r = self.search_s(
- dn,ldap.SCOPE_BASE,None,[attrname]
- )
- except (ldap.NO_SUCH_OBJECT,ldap.NO_SUCH_ATTRIBUTE,ldap.INSUFFICIENT_ACCESS):
- r = []
- except ldap.UNDEFINED_TYPE:
- return None
- try:
- if r:
- e = ldap.cidict.cidict(r[0][1])
- search_subschemasubentry_dn = e.get(attrname,[None])[0]
- if search_subschemasubentry_dn is None:
- if dn:
- # Try to find sub schema sub entry in root DSE
- return self.search_subschemasubentry_s(dn=empty_dn)
- else:
- # If dn was already root DSE we can return here
- return None
- else:
- # With legacy bytes mode, return bytes; otherwise, since this is a DN,
- # RFCs impose that the field value *can* be decoded to UTF-8.
- return self._unbytesify_text_value(search_subschemasubentry_dn)
- except IndexError:
- return None
-
- def read_s(self,dn,filterstr=None,attrlist=None,serverctrls=None,clientctrls=None,timeout=-1):
- """
- Reads and returns a single entry specified by `dn'.
-
- Other attributes just like those passed to `search_ext_s()'
- """
- r = self.search_ext_s(
- dn,
- ldap.SCOPE_BASE,
- filterstr,
- attrlist=attrlist,
- serverctrls=serverctrls,
- clientctrls=clientctrls,
- timeout=timeout,
- )
- if r:
- return r[0][1]
- else:
- return None
-
- def read_subschemasubentry_s(self,subschemasubentry_dn,attrs=None):
- """
- Returns the sub schema sub entry's data
- """
- if self.bytes_mode:
- filterstr = b'(objectClass=subschema)'
- if attrs is None:
- attrs = [attr.encode('utf-8') for attr in SCHEMA_ATTRS]
- else:
- filterstr = u'(objectClass=subschema)'
- if attrs is None:
- attrs = SCHEMA_ATTRS
- try:
- subschemasubentry = self.read_s(
- subschemasubentry_dn,
- filterstr=filterstr,
- attrlist=attrs
- )
- except ldap.NO_SUCH_OBJECT:
- return None
- else:
- return subschemasubentry
-
- def find_unique_entry(self,base,scope=ldap.SCOPE_SUBTREE,filterstr=None,attrlist=None,attrsonly=0,serverctrls=None,clientctrls=None,timeout=-1):
- """
- Returns a unique entry, raises exception if not unique
- """
- r = self.search_ext_s(
- base,
- scope,
- filterstr,
- attrlist=attrlist,
- attrsonly=attrsonly,
- serverctrls=serverctrls,
- clientctrls=clientctrls,
- timeout=timeout,
- sizelimit=2,
- )
- if len(r)!=1:
- raise NO_UNIQUE_ENTRY('No or non-unique search result for %s' % (repr(filterstr)))
- return r[0]
-
- def read_rootdse_s(self, filterstr=None, attrlist=None):
- """
- convenience wrapper around read_s() for reading rootDSE
- """
- if self.bytes_mode:
- base = b''
- attrlist = attrlist or [b'*', b'+']
- else:
- base = u''
- attrlist = attrlist or [u'*', u'+']
- ldap_rootdse = self.read_s(
- base,
- filterstr=filterstr,
- attrlist=attrlist,
- )
- return ldap_rootdse # read_rootdse_s()
-
- 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
- """
- if self.bytes_mode:
- name = b'namingContexts'
- else:
- name = u'namingContexts'
- return self.read_rootdse_s(
- attrlist=[name]
- ).get(name, [])
-
-
- class ReconnectLDAPObject(SimpleLDAPObject):
- """
- In case of server failure (ldap.SERVER_DOWN) the implementations
- of all synchronous operation methods (search_s() etc.) are doing
- an automatic reconnect and rebind and will retry the very same
- operation.
-
- This is very handy for broken LDAP server implementations
- (e.g. in Lotus Domino) which drop connections very often making
- it impossible to have a long-lasting control flow in the
- application.
- """
-
- __transient_attrs__ = {
- '_l',
- '_ldap_object_lock',
- '_trace_file',
- '_reconnect_lock',
- '_last_bind',
- }
-
- def __init__(
- self,uri,
- trace_level=0,trace_file=None,trace_stack_limit=5,bytes_mode=None,
- bytes_strictness=None, retry_max=1, retry_delay=60.0
- ):
- """
- Parameters like SimpleLDAPObject.__init__() with these
- additional arguments:
-
- retry_max
- Maximum count of reconnect trials
- retry_delay
- Time span to wait between two reconnect trials
- """
- self._uri = uri
- self._options = []
- self._last_bind = None
- SimpleLDAPObject.__init__(self, uri, trace_level, trace_file,
- trace_stack_limit, bytes_mode,
- bytes_strictness=bytes_strictness)
- self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self)))
- self._retry_max = retry_max
- self._retry_delay = retry_delay
- self._start_tls = 0
- self._reconnects_done = 0
-
- def __getstate__(self):
- """return data representation for pickled object"""
- state = {
- k: v
- for k,v in self.__dict__.items()
- if k not in self.__transient_attrs__
- }
- state['_last_bind'] = self._last_bind[0].__name__, self._last_bind[1], self._last_bind[2]
- return state
-
- def __setstate__(self,d):
- """set up the object from pickled data"""
- hardfail = d.get('bytes_mode_hardfail')
- if hardfail:
- d.setdefault('bytes_strictness', 'error')
- else:
- d.setdefault('bytes_strictness', 'warn')
- self.__dict__.update(d)
- self._last_bind = getattr(SimpleLDAPObject, self._last_bind[0]), self._last_bind[1], self._last_bind[2]
- self._ldap_object_lock = self._ldap_lock()
- self._reconnect_lock = ldap.LDAPLock(desc='reconnect lock within %s' % (repr(self)))
- # XXX cannot pickle file, use default trace file
- 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 _apply_last_bind(self):
- if self._last_bind!=None:
- func,args,kwargs = self._last_bind
- func(self,*args,**kwargs)
- else:
- # Send explicit anon simple bind request to provoke ldap.SERVER_DOWN in method reconnect()
- SimpleLDAPObject.simple_bind_s(self, None, None)
-
- def _restore_options(self):
- """Restore all recorded options"""
- for k,v in self._options:
- SimpleLDAPObject.set_option(self,k,v)
-
- 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):
- # Drop and clean up old connection completely
- # Reconnect
- self._reconnect_lock.acquire()
- try:
- reconnect_counter = retry_max
- 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' % (
- counter_text,uri
- ))
- try:
- # Do the connect
- self._l = ldap.functions._ldap_function_call(ldap._ldap_module_lock,_ldap.initialize,uri)
- self._restore_options()
- # StartTLS extended operation in case this was called before
- if self._start_tls:
- SimpleLDAPObject.start_tls_s(self)
- # Repeat last simple or SASL bind
- self._apply_last_bind()
- except (ldap.SERVER_DOWN,ldap.TIMEOUT):
- if __debug__ and self._trace_level>=1:
- self._trace_file.write('*** %s reconnect to %s failed\n' % (
- counter_text,uri
- ))
- reconnect_counter = reconnect_counter-1
- if not reconnect_counter:
- raise
- if __debug__ and self._trace_level>=1:
- self._trace_file.write('=> delay %s...\n' % (retry_delay))
- time.sleep(retry_delay)
- SimpleLDAPObject.unbind_s(self)
- else:
- if __debug__ and self._trace_level>=1:
- self._trace_file.write('*** %s reconnect to %s successful => repeat last operation\n' % (
- counter_text,uri
- ))
- self._reconnects_done = self._reconnects_done + 1
- break
- finally:
- self._reconnect_lock.release()
- return # reconnect()
-
- def _apply_method_s(self,func,*args,**kwargs):
- if not hasattr(self,'_l'):
- self.reconnect(self._uri,retry_max=self._retry_max,retry_delay=self._retry_delay)
- 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)
- # Re-try last operation
- return func(self,*args,**kwargs)
-
- def set_option(self,option,invalue):
- self._options.append((option,invalue))
- return SimpleLDAPObject.set_option(self,option,invalue)
-
- def bind_s(self,*args,**kwargs):
- res = self._apply_method_s(SimpleLDAPObject.bind_s,*args,**kwargs)
- self._store_last_bind(SimpleLDAPObject.bind_s,*args,**kwargs)
- return res
-
- def simple_bind_s(self,*args,**kwargs):
- res = self._apply_method_s(SimpleLDAPObject.simple_bind_s,*args,**kwargs)
- self._store_last_bind(SimpleLDAPObject.simple_bind_s,*args,**kwargs)
- return res
-
- def start_tls_s(self,*args,**kwargs):
- res = self._apply_method_s(SimpleLDAPObject.start_tls_s,*args,**kwargs)
- self._start_tls = 1
- return res
-
- def sasl_interactive_bind_s(self,*args,**kwargs):
- """
- sasl_interactive_bind_s(who, auth) -> None
- """
- res = self._apply_method_s(SimpleLDAPObject.sasl_interactive_bind_s,*args,**kwargs)
- self._store_last_bind(SimpleLDAPObject.sasl_interactive_bind_s,*args,**kwargs)
- return res
-
- def sasl_bind_s(self,*args,**kwargs):
- res = self._apply_method_s(SimpleLDAPObject.sasl_bind_s,*args,**kwargs)
- self._store_last_bind(SimpleLDAPObject.sasl_bind_s,*args,**kwargs)
- return res
-
- def add_ext_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.add_ext_s,*args,**kwargs)
-
- def cancel_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.cancel_s,*args,**kwargs)
-
- def compare_ext_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.compare_ext_s,*args,**kwargs)
-
- def delete_ext_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.delete_ext_s,*args,**kwargs)
-
- def extop_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.extop_s,*args,**kwargs)
-
- def modify_ext_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.modify_ext_s,*args,**kwargs)
-
- def rename_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.rename_s,*args,**kwargs)
-
- def search_ext_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.search_ext_s,*args,**kwargs)
-
- def whoami_s(self,*args,**kwargs):
- return self._apply_method_s(SimpleLDAPObject.whoami_s,*args,**kwargs)
-
-
- # The class called LDAPObject will be used as default for
- # ldap.open() and ldap.initialize()
- LDAPObject = SimpleLDAPObject
|