From 0dc11e527e0420631da0ecde3dd28b54352dfca6 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Thu, 3 Nov 2022 16:37:36 +0100 Subject: [PATCH] Synchronizing memberships in groups separatly --- lib/pp_admintools/app/ldap.py | 40 ++++--- lib/pp_admintools/app/mirror_ldap.py | 152 ++++++++++++++++++++++----- 2 files changed, 154 insertions(+), 38 deletions(-) diff --git a/lib/pp_admintools/app/ldap.py b/lib/pp_admintools/app/ldap.py index 729056f..aec3562 100644 --- a/lib/pp_admintools/app/ldap.py +++ b/lib/pp_admintools/app/ldap.py @@ -54,7 +54,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.10.5' +__version__ = '0.10.6' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -1134,13 +1134,14 @@ class BaseLdapApplication(BaseDPXApplication): return result # ------------------------------------------------------------------------- - def normalized_attributes(self, entry): + def normalized_attributes(self, entry, omit_members=False, omit_memberof=False): attribs = CIDict() for attrib in entry['attributes']: values = entry['attributes'][attrib] - if attrib.lower() == 'objectclass': + a_lc = attrib.lower() + if a_lc == 'objectclass': if 'objectClass' not in attribs: attribs['objectClass'] = CIStringSet() if is_sequence(values): @@ -1149,6 +1150,10 @@ class BaseLdapApplication(BaseDPXApplication): else: attribs['objectClass'].add(values) else: + if omit_members and a_lc in ('member', 'uniquemember'): + continue + if omit_memberof and a_lc == 'memberof': + continue if attrib not in attribs: attribs[attrib] = [] if is_sequence(values): @@ -1180,7 +1185,7 @@ class BaseLdapApplication(BaseDPXApplication): req_status, req_result, req_response, req_whatever = ldap.add( dn, object_class=object_classes, attributes=target_entry) except LDAPException as e: - msg = _("Creation of entry {dn!r} NOT successfull - {c}: {e}").format( + msg = _("Creation of entry {dn!r} was NOT successfull - {c}: {e}").format( dn=dn, c=e.__class__.__name__, e=e) msg += '\nobjectClasses:\n' + pp(object_classes) msg += "\nAttributes:\n" + pp(target_entry) @@ -1200,8 +1205,10 @@ class BaseLdapApplication(BaseDPXApplication): LOG.debug(_("Result of creating:") + '\n' + pp(req_result)) if not req_status: - msg = _("Creation NOT successful: {desc} - {msg}").format( - desc=req_result['description'], msg=req_result['message'].strip()) + msg = _("Creation of entry {dn!r} was NOT successful: {desc} - {msg}").format( + dn=dn, desc=req_result['description'], msg=req_result['message'].strip()) + msg += '\nobjectClasses:\n' + pp(object_classes) + msg += "\nAttributes:\n" + pp(target_entry) raise WriteLDAPItemError(msg) LOG.debug(_('Creation successful.')) @@ -1226,7 +1233,8 @@ class BaseLdapApplication(BaseDPXApplication): try: req_status, req_result, req_response, req_whatever = ldap.modify(dn, changes) except LDAPException as e: - msg = _("Modification NOT successfull - {c}: {e}").format(c=e.__class__.__name__, e=e) + msg = _("Modification of {dn!r} was NOT successfull - {c}: {e}").format( + dn=dn, c=e.__class__.__name__, e=e) msg += '\n' + _("Changes:") + '\n' + pp(changes) raise WriteLDAPItemError(msg) @@ -1244,8 +1252,9 @@ class BaseLdapApplication(BaseDPXApplication): LOG.debug(_("Result of modifying:") + '\n' + pp(req_result)) if not req_status: - msg = _("Modification NOT successful: {desc} - {msg}").format( - desc=req_result['description'], msg=req_result['message'].strip()) + msg = _("Modification of {dn!r} was NOT successful: {desc} - {msg}").format( + dn=dn, desc=req_result['description'], msg=req_result['message'].strip()) + msg += '\n' + _("Changes:") + '\n' + pp(changes) raise WriteLDAPItemError(msg) LOG.debug(_('Modification successful.')) @@ -1269,7 +1278,8 @@ class BaseLdapApplication(BaseDPXApplication): try: req_status, req_result, req_response, req_whatever = ldap.delete(dn) except LDAPException as e: - msg = _("Deletion NOT successfull - {c}: {e}").format(c=e.__class__.__name__, e=e) + msg = _("Deletion of {dn!r} was NOT successfull - {c}: {e}").format( + c=e.__class__.__name__, e=e) raise DeleteLDAPItemError(msg) if self.verbose > 1: @@ -1278,8 +1288,8 @@ class BaseLdapApplication(BaseDPXApplication): LOG.debug(_("Result of deletion:") + '\n' + pp(req_result)) if not req_status: - msg = _("Deletion NOT successful: {desc} - {msg}").format( - desc=req_result['description'], msg=req_result['message'].strip()) + msg = _("Deletion of {dn!r} was NOT successful: {desc} - {msg}").format( + dn=dn, desc=req_result['description'], msg=req_result['message'].strip()) raise DeleteLDAPItemError(msg) LOG.debug(_('Deletion successful.')) @@ -1499,9 +1509,13 @@ class BaseLdapApplication(BaseDPXApplication): LOG.debug(msg) continue + old_values = [] + for value in tgt_attribs[attrib_name]: + old_values.append(value) + if attrib_name not in changes: changes[attrib_name] = [] - changes[attrib_name].append((MODIFY_DELETE, )) + changes[attrib_name].append((MODIFY_DELETE, old_values)) return changes diff --git a/lib/pp_admintools/app/mirror_ldap.py b/lib/pp_admintools/app/mirror_ldap.py index 9197965..480d152 100644 --- a/lib/pp_admintools/app/mirror_ldap.py +++ b/lib/pp_admintools/app/mirror_ldap.py @@ -39,7 +39,7 @@ from .ldap import BaseLdapApplication from ..argparse_actions import NonNegativeItegerOptionAction from ..argparse_actions import LimitedFloatOptionAction -__version__ = '0.8.1' +__version__ = '0.9.1' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -79,6 +79,8 @@ class MirrorLdapApplication(BaseLdapApplication): self.src_dns = CIDict() self.src_struct_dns = CIStringSet() + self.mirrored_dns = CIStringSet() + self.tgt_dns_current = CIDict() self.tgt_struct_dns_current = CIStringSet() @@ -268,6 +270,9 @@ class MirrorLdapApplication(BaseLdapApplication): self.clean_tgt_struct_entries() self.mirror_struct_entries() self.mirror_non_struct_entries() + self.update_memberships() + + self.empty_line() msg = ngettext( "{:>5} entry deleted.", "{:>5} entries deleted.", self.total_deleted).format( self.total_deleted) @@ -447,7 +452,8 @@ class MirrorLdapApplication(BaseLdapApplication): if parent_dn not in self.sync_entry_dns: self.sync_entry_dns.append(parent_dn) - LOG.debug("DNs of sync entries:\n" + pp(self.sync_entry_dns)) + if self.verbose > 1: + LOG.debug("DNs of sync entries:\n" + pp(self.sync_entry_dns)) # ------------------------------------------------------------------------- def register_dn_tokens(self, dn, entry, registry): @@ -511,7 +517,8 @@ class MirrorLdapApplication(BaseLdapApplication): continue if dn in self.keep_entry_dns: - LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) continue self.empty_line() @@ -564,11 +571,13 @@ class MirrorLdapApplication(BaseLdapApplication): continue if dn in self.keep_entry_dns: - LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) continue if dn in self.sync_entry_dns: - LOG.debug(_("Entry {!r} is set to be synchronized.").format(dn)) + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be synchronized.").format(dn)) continue self.empty_line() @@ -605,36 +614,40 @@ class MirrorLdapApplication(BaseLdapApplication): for dn in dns: - if self.verbose: + if self.verbose > 1: self.empty_line() if dn in self.keep_entry_dns: - LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) continue - LOG.debug(_("Mirroring entry {!r} ...").format(dn)) + if self.verbose > 1: + LOG.debug(_("Mirroring entry {!r} ...").format(dn)) src_entry = self.get_entry(dn, self.src_instance, attributes) if not src_entry: msg = _("Did not found {!r} in the source LDAP.").format(dn) LOG.warn(msg) continue - src_attribs = self.normalized_attributes(src_entry) + src_attribs = self.normalized_attributes( + src_entry, omit_members=True, omit_memberof=True) src_oclasses = src_attribs['objectClass'].as_list() src_attribs_dict = src_attribs.dict() src_attribs_dict['objectClass'] = src_oclasses - if self.verbose > 1: + if self.verbose > 2: LOG.debug("Got source entry:\n" + pp(src_attribs_dict)) tgt_entry = self.get_entry(dn, self.tgt_instance, attributes) if tgt_entry: - tgt_attribs = self.normalized_attributes(tgt_entry) + tgt_attribs = self.normalized_attributes( + tgt_entry, omit_members=True, omit_memberof=True) tgt_oclasses = tgt_attribs['objectClass'].as_list() tgt_attribs_dict = tgt_attribs.dict() tgt_attribs_dict['objectClass'] = tgt_oclasses - if self.verbose > 1: + if self.verbose > 2: LOG.debug("Got target entry:\n" + pp(tgt_attribs_dict)) changes = self.generate_modify_data(dn, src_attribs, tgt_attribs) @@ -647,10 +660,12 @@ class MirrorLdapApplication(BaseLdapApplication): self.mirrored_entries += 1 count += 1 self.total_updated += 1 + self.mirrored_dns.add(dn) if self.wait_after_write and not self.simulate: time.sleep(self.wait_after_write) else: - LOG.debug(_("No changes necessary on DN {!r}.").format(dn)) + if self.verbose > 1: + LOG.debug(_("No changes necessary on DN {!r}.").format(dn)) continue else: @@ -665,6 +680,8 @@ class MirrorLdapApplication(BaseLdapApplication): self.add_entry(self.tgt_instance, dn, object_classes, target_entry) self.mirrored_entries += 1 count += 1 + self.total_created += 1 + self.mirrored_dns.add(dn) if self.wait_after_write and not self.simulate: time.sleep(self.wait_after_write) @@ -701,35 +718,40 @@ class MirrorLdapApplication(BaseLdapApplication): if dn in self.src_struct_dns: continue + if self.verbose > 1: + self.empty_line() + if dn in self.keep_entry_dns: - LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) continue - if self.verbose: - self.empty_line() - LOG.debug(_("Mirroring entry {!r} ...").format(dn)) + if self.verbose > 1: + LOG.debug(_("Mirroring entry {!r} ...").format(dn)) src_entry = self.get_entry(dn, self.src_instance, attributes) if not src_entry: msg = _("Did not found {!r} in the source LDAP.").format(dn) LOG.warn(msg) continue - src_attribs = self.normalized_attributes(src_entry) + src_attribs = self.normalized_attributes( + src_entry, omit_members=True, omit_memberof=True) src_oclasses = src_attribs['objectClass'].as_list() src_attribs_dict = src_attribs.dict() src_attribs_dict['objectClass'] = src_oclasses - if self.verbose > 1: + if self.verbose > 2: LOG.debug("Got source entry:\n" + pp(src_attribs_dict)) tgt_entry = self.get_entry(dn, self.tgt_instance, attributes) if tgt_entry: - tgt_attribs = self.normalized_attributes(tgt_entry) + tgt_attribs = self.normalized_attributes( + tgt_entry, omit_members=True, omit_memberof=True) tgt_oclasses = tgt_attribs['objectClass'].as_list() tgt_attribs_dict = tgt_attribs.dict() tgt_attribs_dict['objectClass'] = tgt_oclasses - if self.verbose > 1: + if self.verbose > 2: LOG.debug("Got target entry:\n" + pp(tgt_attribs_dict)) changes = self.generate_modify_data(dn, src_attribs, tgt_attribs) @@ -742,10 +764,12 @@ class MirrorLdapApplication(BaseLdapApplication): self.mirrored_entries += 1 count += 1 self.total_updated += 1 + self.mirrored_dns.add(dn) if self.wait_after_write and not self.simulate: time.sleep(self.wait_after_write) else: - LOG.debug(_("No changes necessary on DN {!r}.").format(dn)) + if self.verbose > 1: + LOG.debug(_("No changes necessary on DN {!r}.").format(dn)) continue else: @@ -761,6 +785,7 @@ class MirrorLdapApplication(BaseLdapApplication): self.mirrored_entries += 1 count += 1 self.total_created += 1 + self.mirrored_dns.add(dn) if self.wait_after_write and not self.simulate: time.sleep(self.wait_after_write) @@ -770,11 +795,88 @@ class MirrorLdapApplication(BaseLdapApplication): if count: self.empty_line() msg = ngettext( - "Mirrored one none structural entry in target LDAP instance.", - "Mirrored {no} none structural entries to target LDAP instance.", + "Mirrored one non-structural entry in target LDAP instance.", + "Mirrored {no} non-structural entries to target LDAP instance.", + count).format(no=count) + else: + msg = _("Mirrored none not-structural entries to target LDAP instance.") + LOG.info(msg) + + # ------------------------------------------------------------------------- + def update_memberships(self): + """Updating all 'member' and 'uniqueMember' attributes.""" + self.empty_line() + + self.line(color='CYAN') + LOG.info(_("Mirroring members in group entries from source to target LDAP instance.")) + if not self.quiet: + time.sleep(2) + + dns = sorted(list(self.src_dns.keys()), key=cmp_to_key(self.compare_ldap_dns)) + + count = 0 + + attributes = ['member', 'uniqueMember'] + + for dn in dns: + + if dn in self.keep_entry_dns: + if self.verbose > 1: + LOG.debug(_("Entry {!r} is set to be kept.").format(dn)) + continue + + if self.verbose > 1: + self.empty_line() + if self.verbose > 1: + LOG.debug(_("Mirroring entry {!r} ...").format(dn)) + + src_entry = self.get_entry(dn, self.src_instance, attributes) + if not src_entry: + msg = _("Did not found {!r} in the source LDAP.").format(dn) + LOG.warn(msg) + continue + src_attribs = self.normalized_attributes(src_entry, omit_memberof=True) + src_attribs_dict = src_attribs.dict() + if self.verbose > 2: + LOG.debug("Got source entry:\n" + pp(src_attribs_dict)) + + tgt_entry = self.get_entry(dn, self.tgt_instance, attributes) + if not tgt_entry: + LOG.warn(_("Target entry {!r} not found.").format(dn)) + continue + + tgt_attribs = self.normalized_attributes(tgt_entry, omit_memberof=True) + tgt_attribs_dict = tgt_attribs.dict() + + if self.verbose > 2: + LOG.debug("Got target entry:\n" + pp(tgt_attribs_dict)) + + changes = self.generate_modify_data(dn, src_attribs, tgt_attribs) + if changes: + self.empty_line() + LOG.info(_("Modifying entry {!r} ...").format(dn)) + msg = _("Got modify data for DN {!r}:").format(dn) + LOG.debug(msg + '\n' + pp(changes)) + self.modify_entry(self.tgt_instance, dn, changes) + self.mirrored_entries += 1 + count += 1 + if dn not in self.mirrored_dns: + self.total_updated += 1 + if self.wait_after_write and not self.simulate: + time.sleep(self.wait_after_write * 2) + else: + if self.verbose > 1: + LOG.debug(_("No changes necessary on DN {!r}.").format(dn)) + continue + + if count: + self.empty_line() + msg = ngettext( + "Mirrored one group entry in target LDAP instance.", + "Mirrored {no} group entries to target LDAP instance.", count).format(no=count) else: - msg = _("Mirrored non structural entries to target LDAP instance.") + msg = _("Mirrored no group entries to target LDAP instance.") LOG.info(msg) -- 2.39.5