]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Synchronizing memberships in groups separatly
authorFrank Brehm <frank@brehm-online.com>
Thu, 3 Nov 2022 15:37:36 +0000 (16:37 +0100)
committerFrank Brehm <frank@brehm-online.com>
Thu, 3 Nov 2022 15:37:36 +0000 (16:37 +0100)
lib/pp_admintools/app/ldap.py
lib/pp_admintools/app/mirror_ldap.py

index 729056fcbfce5b31e37710d31113bc58c00afdda..aec35628a65cc8733bc77b9fc7dbdcb08fec971c 100644 (file)
@@ -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
 
index 91979654244e2275cc5d2ffabf8ec8fb7db3ef49..480d152224399e392b20a8d31d0774214d2ebbd7 100644 (file)
@@ -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)