From 23c55cc29e8ca1c5b14994b8cc78406ba6ee093d Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Tue, 17 Nov 2020 18:14:20 +0100 Subject: [PATCH] Continuing in migration --- lib/ldap_migration/__init__.py | 154 ++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/lib/ldap_migration/__init__.py b/lib/ldap_migration/__init__.py index 3a6dc8b..108f2b8 100644 --- a/lib/ldap_migration/__init__.py +++ b/lib/ldap_migration/__init__.py @@ -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): -- 2.39.5