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
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'
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):
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)
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):