]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Generating modification data
authorFrank Brehm <frank.brehm@pixelpark.com>
Wed, 24 May 2023 15:52:25 +0000 (17:52 +0200)
committerFrank Brehm <frank.brehm@pixelpark.com>
Wed, 24 May 2023 15:52:25 +0000 (17:52 +0200)
lib/pp_admintools/app/barracuda_sync.py

index 8c725eb38ea3cf002018578c393bf5c88eecc947..0f36812f58b9528bb56a2fa65e35fc2786f0e265 100644 (file)
@@ -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):