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
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 = _(
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):
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):