From f3923b910dda6067f8c47aa3b9484e8a7e87ba18 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Thu, 20 Oct 2022 14:21:39 +0200 Subject: [PATCH] Retrieving existing entries in target LDAP instance for mirror-ldap --- lib/pp_admintools/app/__init__.py | 8 ++- lib/pp_admintools/app/ldap.py | 96 +++++++++++++++++++++++++++- lib/pp_admintools/app/mirror_ldap.py | 58 ++++++++++------- 3 files changed, 136 insertions(+), 26 deletions(-) diff --git a/lib/pp_admintools/app/__init__.py b/lib/pp_admintools/app/__init__.py index fa23a62..68c128d 100644 --- a/lib/pp_admintools/app/__init__.py +++ b/lib/pp_admintools/app/__init__.py @@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__) _ = XLATOR.gettext ngettext = XLATOR.ngettext -__version__ = '0.4.2' +__version__ = '0.5.0' # ============================================================================= @@ -71,6 +71,12 @@ class BaseDPXApplication(FbConfigApplication): env_prefix=env_prefix, config_dir=config_dir ) + # ------------------------------------------------------------------------- + def empty_line(self): + """Print out an empty line on stdout, if not in quiet mode.""" + if not self.quiet: + print() + # ------------------------------------------------------------------------- def post_init(self): """ diff --git a/lib/pp_admintools/app/ldap.py b/lib/pp_admintools/app/ldap.py index 0b186ca..e2a1ac6 100644 --- a/lib/pp_admintools/app/ldap.py +++ b/lib/pp_admintools/app/ldap.py @@ -18,6 +18,8 @@ try: except ImportError: from pathlib2 import Path +from functools import cmp_to_key + # Third party modules from ldap3 import Server, Connection, DSA, IP_V4_PREFERRED, SAFE_SYNC # from ldap3 import ALL @@ -51,7 +53,7 @@ from ..config.ldap import LdapConnectionInfo, LdapConfiguration # rom ..config.ldap import DEFAULT_PORT_LDAP, DEFAULT_PORT_LDAPS from ..config.ldap import DEFAULT_TIMEOUT -__version__ = '0.8.2' +__version__ = '0.9.0' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -177,9 +179,65 @@ class BaseLdapApplication(BaseDPXApplication): pattern_re_uid = r'^[a-z](?:[a-z0-9\-_.]*[a-z0-9])?$' re_uid = re.compile(pattern_re_uid, re.IGNORECASE) + pattern_dn_separator = r'\s*,\s*' + re_dn_separator = re.compile(pattern_dn_separator) + person_object_classes = FrozenCIStringSet([ 'account', 'inetOrgPerson', 'inetUser', 'posixAccount', 'sambaSamAccount']) + # ------------------------------------------------------------------------- + @classmethod + def compare_ldap_dns(cls, x, y): + + # Check for empty DNs + if x is None and y is None: + return 0 + if x is None: + return -1 + if y is None: + return 1 + + # Check for empty FQDNs + xs = str(x).strip() + ys = str(y).strip() + + if xs == '' and ys == '': + return 0 + if xs == '': + return -1 + if ys == '': + return 1 + + tokens_x = cls.re_dn_separator.split(xs) + tokens_x_rev = list(reversed(tokens_x)) + tokens_y = cls.re_dn_separator.split(ys) + tokens_y_rev = list(reversed(tokens_y)) + + if ','.join(tokens_x).lower() == ','.join(tokens_y).lower(): + return 0 + + i = 0 + while True: + j = i + 1 + if j > len(tokens_x_rev) or j > len(tokens_y_rev): + if j <= len(tokens_x_rev): + return 1 + if j <= len(tokens_y_rev): + return -1 + return 0 + token_x = tokens_x_rev[i] + token_y = tokens_y_rev[i] + i += 1 + if token_x.lower() > token_y.lower(): + return 1 + if token_x.lower() < token_y.lower(): + return -1 + + # Should never come to here ... + LOG.warn("Reached a point, where I never should come.") + LOG.debug("Compared x: {x!r} <=> y: {y!r}".format(x=x, y=y)) + return 0 + # ------------------------------------------------------------------------- def __init__( self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None, @@ -702,6 +760,42 @@ class BaseLdapApplication(BaseDPXApplication): LOG.debug(_("Disconnecting from LDAP server {!r} ...").format(connect_info.url)) del self.ldap_server[inst] + # ------------------------------------------------------------------------- + def get_all_entry_dns(self, inst): + """Get DNs of all entries in the given LDAP instance and sort them.""" + + connect_info = self.cfg.ldap_connection[inst] + base_dn = connect_info.base_dn + ldap = self.ldap_connection[inst] + + result = [] + attributes = ['dn'] + ldap_filter = '(objectClass=*)' + + req_status, req_result, req_response, req_whatever = ldap.search( + search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter, + get_operational_attributes=False, attributes=attributes, + time_limit=self.cfg.ldap_timeout) + + if req_status: + if self.verbose > 5: + msg = _("Result of searching for DNs of all entries:") + LOG.debug(msg + '\n' + pp(req_result)) + for entry in req_response: + if self.verbose > 4: + LOG.debug(_("Got a response entry:") + ' ' + pp(entry)) + result.append(entry['dn']) + + else: + LOG.warn("Got no entry DNs.") + + if result: + result = sorted(result, key=cmp_to_key(self.compare_ldap_dns)) + + if self.verbose > 2 and result: + LOG.debug(_("Result:") + '\n' + pp(result)) + return result + # ------------------------------------------------------------------------- def get_user_dn(self, user, inst): diff --git a/lib/pp_admintools/app/mirror_ldap.py b/lib/pp_admintools/app/mirror_ldap.py index c682ff2..9f04ef9 100644 --- a/lib/pp_admintools/app/mirror_ldap.py +++ b/lib/pp_admintools/app/mirror_ldap.py @@ -28,7 +28,7 @@ from .ldap import BaseLdapApplication from ..argparse_actions import NonNegativeItegerOptionAction from ..argparse_actions import LimitedFloatOptionAction -__version__ = '0.2.2' +__version__ = '0.3.0' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -60,9 +60,13 @@ class MirrorLdapApplication(BaseLdapApplication): def __init__(self, appname=None, base_dir=None): self.src_instance = None - self.src = None self.tgt_instance = None - self.tgt = None + + self.src_connect_info = None + self.tgt_connect_info = None + + self.src_dns = [] + self.tgt_dns_current = [] self.limit = 0 self.wait_after_write = self.default_wait_after_write @@ -130,7 +134,7 @@ class MirrorLdapApplication(BaseLdapApplication): limit = getattr(self.args, 'limit', 0) if limit: - print() + self.empty_line() if self.simulate: self.limit = limit LOG.warn(_( @@ -140,7 +144,7 @@ class MirrorLdapApplication(BaseLdapApplication): LOG.error(_( "Limition the number of entries for mirroring may " "only be done in simulation mode.")) - print() + self.empty_line() self.arg_parser.print_usage(sys.stdout) self.exit(1) @@ -152,6 +156,7 @@ class MirrorLdapApplication(BaseLdapApplication): def _check_source_instance(self): tgt_name = self.ldap_instances[0] + self.tgt_connect_info = self.cfg.ldap_connection[tgt_name] LOG.debug(_( "Checking mirror source instance for target instance {!r} ...").format(tgt_name)) @@ -165,6 +170,7 @@ class MirrorLdapApplication(BaseLdapApplication): "does not exists.").format(src=src_name, tgt=tgt_name) LOG.error(msg) self.exit(3) + self.src_connect_info = self.cfg.ldap_connection[src_name] if tgt_name.lower() == src_name.lower(): msg = _("Error in configuration:") @@ -190,27 +196,11 @@ class MirrorLdapApplication(BaseLdapApplication): self.tgt_instance = tgt_name self.ldap_instances.append(src_name) - # ------------------------------------------------------------------------- - def pre_run(self): - - super(MirrorLdapApplication, self).pre_run() - - self.src = self.ldap_connection[self.src_instance] - self.tgt = self.ldap_connection[self.tgt_instance] - - # ------------------------------------------------------------------------- - def disconnect_all(self): - - self.src = None - self.tgt = None - - super(MirrorLdapApplication, self).disconnect_all() - # ------------------------------------------------------------------------- def _run(self): if not self.quiet and not self.force: - print() + self.empty_line() src_url = self.cfg.ldap_connection[self.src_instance].url tgt_url = self.cfg.ldap_connection[self.tgt_instance].url msg = _( @@ -219,10 +209,30 @@ class MirrorLdapApplication(BaseLdapApplication): src=self.src_instance, src_url=src_url, tgt=self.tgt_instance, tgt_url=tgt_url) self.countdown(number=5, delay=1, prompt=msg) - if not self.quiet: - print() + self.empty_line() LOG.info("I'm walking, yes indeed I'm walking ...") + self.clean_target_instance() + + # ------------------------------------------------------------------------- + def clean_target_instance(self): + """Cleaning the target instance.""" + + self.empty_line() + LOG.info(_( + "Removing all existing entries in target LDAP instance " + "(except the base DN entry, of course).")) + + self.get_current_tgt_entries() + + # ------------------------------------------------------------------------- + def get_current_tgt_entries(self): + """Get DNs of all entries in the target LDAP instance and sort them.""" + + LOG.debug(_("Trying to get DNs of all entries in the target LDAP instance.")) + + result = self.get_all_entry_dns(self.tgt_instance) + # ============================================================================= if __name__ == "__main__": -- 2.39.5