From: Frank Brehm Date: Tue, 30 Jan 2024 17:05:27 +0000 (+0100) Subject: Refactoring LDAP search X-Git-Tag: 1.0.0~1^2~28 X-Git-Url: https://git.uhu-banane.org/?a=commitdiff_plain;h=ce7b2521707cae619bb9805bca6e8dc4dba0fa44;p=pixelpark%2Fpp-admin-tools.git Refactoring LDAP search --- diff --git a/lib/pp_admintools/app/ldap.py b/lib/pp_admintools/app/ldap.py index e7b5b3f..9ddbb49 100644 --- a/lib/pp_admintools/app/ldap.py +++ b/lib/pp_admintools/app/ldap.py @@ -35,11 +35,9 @@ from ldap3 import BASE, SUBTREE from ldap3 import Connection, DSA, IP_V4_PREFERRED, SAFE_SYNC, Server from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE from ldap3.core.exceptions import LDAPBindError -# from ldap3.core.exceptions import LDAPCommunicationError +from ldap3.core.exceptions import LDAPCommunicationError from ldap3.core.exceptions import LDAPException from ldap3.core.exceptions import LDAPSessionTerminatedByServerError -from ldap3.core.exceptions import LDAPSocketOpenError -from ldap3.core.exceptions import LDAPSocketReceiveError # Own modules from . import BaseDPXApplication @@ -54,14 +52,15 @@ from ..config.ldap import DEFAULT_TIMEOUT from ..config.ldap import LdapConfiguration, LdapConnectionInfo # from ..errors import DpxAppError from ..errors import DpxDeleteLdapItemError +from ..errors import DpxFatalLdapError from ..errors import DpxLdapError from ..errors import DpxLdapParseError -from ..errors import DpxLdapReadError +from ..errors import DpxLdapSearchError from ..errors import DpxLdapSessionError from ..errors import DpxWriteLdapItemError from ..xlate import XLATOR, format_list -__version__ = '1.2.0' +__version__ = '1.3.0' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -885,52 +884,155 @@ class BaseLdapApplication(BaseDPXApplication): del self.ldap_server[inst] # ------------------------------------------------------------------------- - def get_all_entries(self, inst, base_dn=None, ldap_filter=None, attributes=None, scope=None): + def search( + self, inst, search_base=None, ldap_filter=None, attributes=None, scope=None, + retries=None, wait_on_error=None, operational_attributes=False): """ - Get all LDAP entries bellow the given BaseDN and the given LDAP filter. + Execute a LDAP search with the given parameters. - If no attributes are given, all attributes are given back. - The result is a hash with the DNs if the resulting entries as keys, and a hash - with the resulting attributes as values. + Returns a tuple with: + * status + * result + * response """ connect_info = self.cfg.ldap_connection[inst] if scope is None: scope = SUBTREE - result = {} + rec_out = '' + if scope == SUBTREE: + rec_out = _('recursive') + ' ' - if not base_dn: - base_dn = connect_info.base_dn + if not search_base: + search_base = connect_info.base_dn if attributes is None: attributes = [ALL_ATTRIBUTES] if ldap_filter is None: ldap_filter = '(objectClass=*)' + if retries is None: + retries = self.retries_on_conn_error + + if wait_on_error is None: + wait_on_error = self.wait_on_conn_error + if self.verbose > 2: msg = _( - 'Searching in {uri}/{bdn} for all entries with filter {fltr!r}, ' - 'giving attributes:').format(uri=connect_info.url, bdn=base_dn, fltr=ldap_filter) + 'Searching in {uri}/{bdn} {rec}for all entries with filter {fltr!r}, ' + 'giving attributes:').format( + uri=connect_info.url, bdn=search_base, rec=rec_out, fltr=ldap_filter) msg += ' ' + format_list(attributes, do_repr=True) LOG.debug(msg) + cur_try = 0 + search_status = None + search_result = None + search_response = None + result = None + + while True: + + cur_try += 1 + result = self._search( + inst, cur_try=cur_try, retries=retries, search_base=search_base, scope=scope, + attributes=attributes, op_attrs=operational_attributes, ldap_filter=ldap_filter, + time_limit=self.cfg.ldap_timeout, wait_on_error=wait_on_error) + + if result is not None: + break + + search_status = result[0] + search_result = result[1] + search_response = result[2] + + if search_status: + if self.verbose > 4: + LOG.debug(_('Result of searching:') + ' ' + pp(search_result)) + if self.verbose > 3: + LOG.debug(_('Got a response entry:') + '\n' + pp(search_response[0])) + + else: + if self.verbose > 3: + LOG.debug(_('Search was not successful.')) + + return result + + # ------------------------------------------------------------------------- + def _search( + self, inst, cur_try, retries, search_base, scope, attributes, + op_attrs, ldap_filter, time_limit, wait_on_error): + """Execute underlaying LDAP search.""" + e_cls = None + e_msg = None + search_status = None + search_result = None + search_response = None + + if self.verbose > 2: + LOG.debug(_('Try number {} for searching ...').format(cur_try)) + if inst not in self.ldap_connection: self.connect_instance(inst) ldap = self.ldap_connection[inst] try: - req_status, req_result, req_response, req_whatever = ldap.search( - search_base=base_dn, search_scope=scope, attributes=attributes, - search_filter=ldap_filter, time_limit=self.cfg.ldap_timeout) + search_status, search_result, search_response, whatever = ldap.search( + search_base=search_base, search_scope=scope, attributes=attributes, + get_operational_attributes=op_attrs, search_filter=ldap_filter, + time_limit=time_limit) + + except LDAPCommunicationError as e: + e_msg = str(e) + e_cls = e.__class__.__name__ + if cur_try > retries: + msg = _('Got a {cls} on searching in LDAP instance {i!r}:').format( + cls=e_cls, i=inst) + ' ' + str(e) + raise DpxLdapSearchError(msg) + LOG.debug(_('Waiting because of a {}:').format(e_cls) + ' ' + e_msg) + time.sleep(wait_on_error) + return None + + except LDAPSessionTerminatedByServerError as e: + msg = _('Session terminated by server on searching on instance {i!r}:').format( + i=inst) + ' ' + str(e) + raise DpxLdapSessionError(msg) + + except LDAPException as e: + msg = _('Got a {cls} on searching in LDAP instance {i!r}:').format( + cls=e.__class__.__name__, i=inst) + ' ' + str(e) + raise DpxFatalLdapError(msg) + finally: if not self.single_session: self.disconnect_instance(inst) - if req_status: - if self.verbose > 4: - LOG.debug(_('Result of searching:') + '\n' + pp(req_result)) + return (search_status, search_result, search_response) - for entry in req_response: + # ------------------------------------------------------------------------- + def get_all_entries(self, inst, base_dn=None, ldap_filter=None, attributes=None, scope=None): + """ + Get all LDAP entries bellow the given BaseDN and the given LDAP filter. + + If no attributes are given, all attributes are given back. + The result is a hash with the DNs if the resulting entries as keys, and a hash + with the resulting attributes as values. + """ + connect_info = self.cfg.ldap_connection[inst] + + result = {} + search_status = None + search_result = None + search_response = None + + if not base_dn: + base_dn = connect_info.base_dn + + (search_status, search_result, search_response) = self.search( + inst, search_base=base_dn, ldap_filter=ldap_filter, attributes=attributes) + + if search_status: + for entry in search_response: dn = entry['dn'] if self.verbose > 3: LOG.debug(_('Found entry {!r}.').format(dn)) @@ -1343,66 +1445,22 @@ class BaseLdapApplication(BaseDPXApplication): def get_entry(self, dn, inst, attributes=None, operational_attributes=False, tries=3): """Get an complete LDAP entry by the given DN in the given instance.""" connect_info = self.cfg.ldap_connection[inst] - - if attributes is None: - attributes = [ALL_ATTRIBUTES] - - sfilter = '(objectClass=*)' - - result = None - if self.verbose > 1: msg = _('Searching DN {dn!r} in {uri}.').format(dn=dn, uri=connect_info.url) LOG.debug(msg) - cur_try = 0 - e_msg = None - while cur_try < tries: - - if inst not in self.ldap_connection: - self.connect_instance(inst) - ldap = self.ldap_connection[inst] - e_msg = None - cur_try += 1 - try: - req_status, req_result, req_response, req_whatever = ldap.search( - search_base=dn, search_scope=BASE, attributes=attributes, - get_operational_attributes=operational_attributes, search_filter=sfilter, - time_limit=self.cfg.ldap_timeout) - - except (LDAPSocketReceiveError, LDAPSocketOpenError) as e: - e_msg = str(e) - if cur_try >= tries: - break - LOG.debug(_('Waiting because of a failing read operation.')) - time.sleep(self.wait_on_conn_error) - - except LDAPSessionTerminatedByServerError as e: - msg = _('Session terminated on reading entry {dn!r} from instance {i!r}:').format( - dn=dn, i=inst) + ' ' + str(e) - raise DpxLdapSessionError(msg) - - finally: - if not self.single_session: - self.disconnect_instance(inst) - - if e_msg: - msg = _('Error on reading entry {dn!r} from instance {inst!r}:').format( - dn=dn, inst=inst) + ' ' + e_msg - raise DpxLdapReadError(msg) + result = None - if req_status: - if self.verbose > 4: - msg = _('Result of searching for DN {dn!r}:').format(dn=dn) - LOG.debug(msg + ' ' + pp(req_result)) - result = req_response[0] - if self.verbose > 3: - LOG.debug(_('Got a response entry:') + '\n' + pp(result)) + (search_status, search_result, search_response) = self.search( + inst=inst, search_base=dn, attributes=attributes, scope=BASE, + operational_attributes=operational_attributes) + if search_status: + result = search_response[0] else: if self.verbose > 3: - msg = _('Entry with DN {dn!r} not found in {uri}.').format( - dn=dn, uri=connect_info.url) + msg = _('Entry with DN {dn!r} not found in {i!r} ({uri}).').format( + dn=dn, i=inst, uri=connect_info.url) LOG.debug(msg) return result