From 1100109d3ce4e9fac40023884897bf946ed9542f Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Wed, 24 May 2023 17:52:25 +0200 Subject: [PATCH] Generating modification data --- lib/pp_admintools/app/barracuda_sync.py | 190 +++++++++++++++++++++++- 1 file changed, 186 insertions(+), 4 deletions(-) diff --git a/lib/pp_admintools/app/barracuda_sync.py b/lib/pp_admintools/app/barracuda_sync.py index 8c725eb..0f36812 100644 --- a/lib/pp_admintools/app/barracuda_sync.py +++ b/lib/pp_admintools/app/barracuda_sync.py @@ -17,18 +17,20 @@ from functools import cmp_to_key from pathlib import Path # Third party modules +from fb_tools.collections import CIDict, CIStringSet from fb_tools.common import pp from fb_tools.handler import BaseHandler from fb_tools.multi_config import DEFAULT_ENCODING # from fb_tools.xlate import format_list from ldap3 import BASE +from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE # Own modules from .ldap import BaseLdapApplication from ..xlate import XLATOR -__version__ = '0.8.6' +__version__ = '0.8.7' LOG = logging.getLogger(__name__) _ = XLATOR.gettext @@ -112,7 +114,7 @@ class BarracudaSyncApp(BaseLdapApplication): self.ldap_mail_dns = {} self.aliases_to_create = [] self.aliases_to_remove = [] - self.aliases_to_modify = [] + self.aliases_to_modify = {} self.ignore_aliases_res = [] desc = _( @@ -525,8 +527,7 @@ class BarracudaSyncApp(BaseLdapApplication): self.eval_entries_to_remove() self.eval_entries_to_create() - - self.aliases_to_modify = [] + self.eval_entries_to_modify() # ------------------------------------------------------------------------- def eval_entries_to_remove(self): @@ -609,6 +610,187 @@ class BarracudaSyncApp(BaseLdapApplication): msg = _('LDAP Entries to create:') + '\n' + pp(self.aliases_to_create) LOG.debug(msg) + # ------------------------------------------------------------------------- + def eval_entries_to_modify(self): + """Evaluate all LDAP entries to modify.""" + LOG.debug(_('Evaluating all LDAP entries to modify ...')) + + self.aliases_to_modify = {} + + for cn in self.existing_aliases.keys(): + local_alias = self.existing_aliases[cn] + mail = local_alias['alias'] + + if self.verbose > 2: + LOG.debug("Evaluating CN {!r} for modifications ...".format(cn)) + + if mail in self.ldap_mail_dns: + if self.verbose > 2: + LOG.debug(_( + 'Mail {!r} already exists in regular LDAP entries.').format(mail)) + continue + + modify_data = None + for dn in self.ldap_aliases: + entry = self.ldap_aliases[dn] + if mail in entry['mail']: + if self.verbose > 2: + LOG.debug("Evaluating DN {!r} for modifications ...".format(dn)) + modify_data = self._create_modify_alias_data(cn, entry) + if modify_data: + self.aliases_to_modify[dn] = modify_data + break + + nr_modify = len(self.aliases_to_modify.keys()) + + if nr_modify: + msg = ngettext( + '{} LDAP entry should be modified.', '{} LDAP entries should be modified.', + nr_modify).format(nr_modify) + else: + msg = _('No LDAP entries should be modified.') + LOG.info(msg) + if self.verbose > 2: + msg = _('LDAP Entries to modified:') + '\n' + pp(self.aliases_to_modify) + LOG.debug(msg) + + # ------------------------------------------------------------------------- + def _create_modify_alias_data(self, cn, existing_entry): + target_entry = self._create_ldap_entry_for_compare(cn) + modify_data = {} + + if self.verbose > 3: + msg = 'Comparing existing entry:\n' + pp(existing_entry) + msg += '\n-> target entry:\n' + pp(target_entry) + LOG.debug(msg) + + for attrib_name in existing_entry.keys(): + attr_changes = self._generate_alias_diff_attribs( + attrib_name, target_entry, existing_entry) + if attr_changes: + modify_data[attrib_name] = attr_changes + + for attrib_name in target_entry.keys(): + if attrib_name in existing_entry: + continue + + add_values = [] + for value in target_entry[attrib_name]: + add_values.append(value) + + if attrib_name not in modify_data: + modify_data[attrib_name] = [] + modify_data[attrib_name].append((MODIFY_ADD, add_values)) + + if not modify_data: + return None + return modify_data + + # ------------------------------------------------------------------------- + def _generate_alias_diff_attribs(self, attrib_name, src_attribs, tgt_attribs): + attr_changes = [] + + if self.verbose > 3: + LOG.debug('Trying to get changes to attribute {!r}.'.format(attrib_name)) + + src_attrib_values = src_attribs[attrib_name] + if attrib_name.lower() == 'objectclass': + src_attrib_values = src_attribs[attrib_name].as_list() + if self.verbose > 4: + LOG.debug('Source values for {!r}:'.format(attrib_name) + '\n' + pp(src_attrib_values)) + if attrib_name in tgt_attribs: + tgt_attrib_values = tgt_attribs[attrib_name] + if attrib_name.lower() == 'objectclass': + tgt_attrib_values = tgt_attribs[attrib_name].as_list() + if self.verbose > 4: + LOG.debug('Target values for {!r}:'.format(attrib_name) + '\n' + pp(tgt_attrib_values)) + values_add = [] + values_del = [] + + for src_val in src_attrib_values: + if src_val not in tgt_attrib_values: + values_add.append(src_val) + for tgt_val in tgt_attrib_values: + if tgt_val not in src_attrib_values: + values_del.append(tgt_val) + + if self.verbose > 4 and values_add: + msg = _('Values to add to attribute {!r}:').format(attrib_name) + LOG.debug(msg + '\n' + pp(values_add)) + + if self.verbose > 4 and values_del: + msg = _('Values to be removed from attribute {!r}:').format(attrib_name) + LOG.debug(msg + '\n' + pp(values_del)) + + if len(values_add) == len(src_attrib_values) and len(values_del) == 0: + if len(values_add): + attr_changes.append((MODIFY_REPLACE, values_add)) + else: + if values_del: + attr_changes.append((MODIFY_DELETE, values_del)) + if values_add: + attr_changes.append((MODIFY_ADD, values_add)) + + else: + attr_changes = [(MODIFY_ADD, src_attrib_values)] + + if self.verbose > 3: + if attr_changes: + msg = _('Changes for attribute {!r}:').format(attrib_name) + msg += '\n' + pp(attr_changes) + else: + msg = _('No changes to attribute {!r}.').format(attrib_name) + LOG.debug(msg) + + return attr_changes + + + # ------------------------------------------------------------------------- + def _create_ldap_entry_for_compare(self, cn): + """Creates an entry analogue to normalized_attributes() by the alias antry.""" + local_alias = self.existing_aliases[cn] + entry = CIDict() + oclasses = CIStringSet() + + oclasses.add('mailRecipient') + oclasses.add('inetResource') + oclasses.add('top') + + # attr types for inetResource: + # - cn ! + # - facsimileTelephoneNumber + # - inetResourceStatus + # - mail + # - postalAddress + # - telephoneNumber + # + # attr types for mailRecipient + # - cn + # - mail + # - mailAccessDomain + # - mailAlternateAddress + # - mailAutoReplyMode + # - mailAutoReplyText + # - mailDeliveryOption + # - mailForwardingAddress + # - mailHost + # - mailMessageStore + # - mailProgramDeliveryInfo + # - mailQuota + # - mailRoutingAddress + # - multiLineDescription + # - uid + # - userPassword + + entry['objectClass'] = oclasses + entry['cn'] = [cn] + entry['mail'] = [local_alias['alias']] + entry['multiLineDescription'] = [] + for target in local_alias['targets']: + entry['multiLineDescription'].append(target) + + return entry + # ------------------------------------------------------------------------- def _run(self): -- 2.39.5