]> Frank Brehm's Git Trees - pixelpark/ldap-migration.git/commitdiff
Continuing in migration
authorFrank Brehm <frank.brehm@pixelpark.com>
Tue, 17 Nov 2020 17:14:20 +0000 (18:14 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Tue, 17 Nov 2020 17:14:20 +0000 (18:14 +0100)
lib/ldap_migration/__init__.py

index 3a6dc8b80c15b806dae94a68a98405cf838ae5eb..108f2b887392d7682462fdbe046140837ef546d7 100644 (file)
@@ -22,6 +22,7 @@ import argparse
 from ldap3 import Server, Connection, ALL, DSA, IP_V4_PREFERRED, SAFE_SYNC
 from ldap3 import BASE, LEVEL, SUBTREE, DEREF_NEVER, DEREF_SEARCH, DEREF_BASE, DEREF_ALWAYS
 from ldap3 import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES
+from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE
 
 from ldap3.core.exceptions import LDAPException
 
@@ -36,7 +37,7 @@ from fb_tools.errors import FbAppError
 from .config import LDAPMigrationConfiguration
 from .idict import CaseInsensitiveDict
 
-__version__ = '0.6.1'
+__version__ = '0.6.2'
 
 LOG = logging.getLogger(__name__)
 CFG_BASENAME = 'ldap-migration.ini'
@@ -775,6 +776,121 @@ class LDAPMigrationApplication(BaseApplication):
 
         self._migrate_entries(self.struct_dns, is_root=True, with_acl=False)
 
+    # -------------------------------------------------------------------------
+    def generate_target_entry(self, src_entry, src_dn, tgt_dn):
+
+        object_classes = []
+        target_entry = {}
+
+        for attribute_name in src_entry['attributes']:
+            if attribute_name.lower() == 'objectclass':
+                for src_oc_name in sorted(src_entry['attributes'][attribute_name], key=str.lower):
+                    if src_oc_name not in self.object_classes:
+                        if self.verbose > 3:
+                            msg = "ObjectClass {oc!r} of sorce entry {dn!r} not found "
+                            msg += "on target LDAP server."
+                            msg = msg.format(oc=src_oc_name, dn=src_dn)
+                            LOG.debug(msg)
+                        continue
+                    tgt_oc_name = self.object_classes.get_key(src_oc_name)
+                    object_classes.append(tgt_oc_name)
+                continue
+            if attribute_name not in self.attribute_types:
+                if self.verbose > 3:
+                    msg = "AttributeType {at!r} of sorce entry {dn!r} not found "
+                    msg += "on target LDAP server."
+                    msg = msg.format(at=attribute_name, dn=src_dn)
+                    LOG.debug(msg)
+                continue
+            tgt_at_name = self.attribute_types.get_key(attribute_name)
+            target_entry[tgt_at_name] = copy.copy(src_entry['attributes'][attribute_name])
+
+        return (object_classes, target_entry)
+
+    # -------------------------------------------------------------------------
+    def generate_modify_data(self, src_entry, tgt_entry, src_dn, tgt_dn):
+
+        changes = {}
+
+        src_obj_classes = CaseInsensitiveDict()
+        src_attributes = CaseInsensitiveDict()
+
+        tgt_obj_classes = CaseInsensitiveDict()
+        tgt_attributes = CaseInsensitiveDict()
+
+        for src_at_name in src_entry['attributes']:
+
+            if src_at_name.lower() == 'objectclass':
+                for src_oc_name in src_entry['attributes'][src_at_name]:
+                    src_obj_classes[src_oc_name] = 0
+            else:
+                src_attributes[src_at_name] = copy.copy(src_entry['attributes'][src_at_name])
+
+        for tgt_at_name in tgt_entry['attributes']:
+
+            if tgt_at_name.lower() == 'objectclass':
+                for tgt_oc_name in tgt_entry['attributes'][tgt_at_name]:
+                    tgt_obj_classes[tgt_oc_name] = 0
+            else:
+                tgt_attributes[tgt_at_name] = copy.copy(tgt_entry['attributes'][tgt_at_name])
+
+        objectclasses_to_add = []
+        for src_oc_name in src_obj_classes.keys():
+            if src_oc_name not in tgt_obj_classes:
+                if self.verbose > 3:
+                    msg = "ObjectClass {oc!r} seems to fail in target entry {dn!r}."
+                    LOG.debug(msg.format(oc=src_oc_name, dn=tgt_dn))
+                if src_oc_name not in self.object_classes:
+                    if self.verbose > 3:
+                        msg = "ObjectClass {oc!r} of sorce entry {dn!r} not found "
+                        msg += "on target LDAP server."
+                        msg = msg.format(oc=src_oc_name, dn=src_dn)
+                        LOG.debug(msg)
+                    continue
+                tgt_oc_name = self.object_classes.get_key(src_oc_name)
+                objectclasses_to_add.append(tgt_oc_name)
+
+        if objectclasses_to_add:
+            if 'objectClass' not in changes:
+                changes['objectClass'] = {}
+            changes['objectClass'] = [(MODIFY_ADD, objectclasses_to_add)]
+
+        for src_at_name in src_attributes.keys():
+            if src_at_name not in self.attribute_types:
+                if self.verbose > 3:
+                    msg = "AttributeType {at!r} of sorce entry {dn!r} not found "
+                    msg += "on target LDAP server."
+                    msg = msg.format(at=src_at_name, dn=src_dn)
+                    LOG.debug(msg)
+                continue
+
+            tgt_at_name = self.attribute_types.get_key(src_at_name)
+            src_value = src_attributes[src_at_name]
+            do_replace = False
+            if tgt_at_name in tgt_attributes:
+                cur_tgt_value = tgt_attributes[tgt_at_name]
+                if self.compare_values(src_value, cur_tgt_value):
+                    if self.verbose > 3:
+                        msg = (
+                            "Attribute {atr!r} of source DN {sdn!r} is equal to "
+                            "target DN {tdn!r}. Won't change.")
+                        LOG.debug(msg.format(atr=tgt_at_name, sdn=src_dn, tdn=tgt_dn))
+                    continue
+                changes[tgt_at_name] = [(MODIFY_REPLACE, src_value)]
+            else:
+                changes[tgt_at_name] = [(MODIFY_ADD, src_value)]
+
+        if changes.keys():
+            if self.verbose > 2:
+                msg = "Changes on target entry {tdn!r}:\n{ch}".format(tdn=tgt_dn, ch=pp(changes))
+                LOG.debug(msg)
+            return changes
+
+        if self.verbose > 2:
+            msg = "No changes on target entry {tdn!r} necessary.".format(tdn=tgt_dn)
+            LOG.debug(msg)
+        return None
+
     # -------------------------------------------------------------------------
     def _migrate_entries(self, cur_hash, is_root=False, with_acl=False):
 
@@ -825,9 +941,18 @@ class LDAPMigrationApplication(BaseApplication):
                     if self.verbose > 2:
                         LOG.debug("Response of searching for target DN {dn!r}:\n{res}".format(
                             dn=tgt_dn, res=pp(target_entry)))
+                    changes = self.generate_modify_data(src_entry, target_entry, src_dn, tgt_dn)
+
                 else:
                     if self.verbose > 2:
                         LOG.debug("Target DN {dn!r} not found.".format(dn=tgt_dn))
+                    (tgt_obj_classes, tgt_entry) = self.generate_target_entry(src_entry, src_dn, tgt_dn)
+                    if self.verbose > 2:
+                        msg = "Generated entry for target DN {dn!r}:\n"
+                        msg += "object classes: {oc}\n"
+                        msg += "entry: {en}"
+                        msg = msg.format(dn=tgt_dn, oc=tgt_obj_classes, en=tgt_entry)
+                        LOG.debug(msg)
 
             else:
                 msg = "Did not found source entry with DN {!r} (WTF?).".format(src_dn)
@@ -836,6 +961,33 @@ class LDAPMigrationApplication(BaseApplication):
         for key in cur_hash['childs'].keys():
             self._migrate_entries(cur_hash['childs'][key], is_root=False, with_acl=with_acl)
 
+    # -------------------------------------------------------------------------
+    def compare_values(self, first, second):
+
+        if is_sequence(first) and not is_sequence(second):
+            return False
+
+        if is_sequence(second) and not is_sequence(first):
+            return False
+
+        if not is_sequence(first):
+            # second is also not an array at this point
+            if first.lower() == second.lower():
+                return True
+            return False
+
+        # Both parameters are values
+        first_array = []
+        for val in sorted(first, key=str.lower):
+            first_array.append(val.lower())
+        second_array = []
+        for val in sorted(second, key=str.lower):
+            second_array.append(val.lower())
+
+        if first_array == second_array:
+            return True
+        return False
+
     # -------------------------------------------------------------------------
     def _run(self):