]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Adding bin/clean-empty-ldap-groups and its application module lib/pp_admintools/app...
authorFrank Brehm <frank.brehm@pixelpark.com>
Wed, 14 Jun 2023 13:30:41 +0000 (15:30 +0200)
committerFrank Brehm <frank.brehm@pixelpark.com>
Wed, 14 Jun 2023 13:30:41 +0000 (15:30 +0200)
bin/clean-empty-ldap-groups [new file with mode: 0755]
lib/pp_admintools/app/clean_empty_ldap_groups.py [new file with mode: 0644]

diff --git a/bin/clean-empty-ldap-groups b/bin/clean-empty-ldap-groups
new file mode 100755 (executable)
index 0000000..0243595
--- /dev/null
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+@summary: An application script for removing empty groups from LDAP.
+
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2023 by Frank Brehm, Berlin
+"""
+from __future__ import print_function
+
+# Standard modules
+import locale
+import os
+import sys
+
+__exp_py_version_major__ = 3
+__min_py_version_minor__ = 6
+
+if sys.version_info[0] != __exp_py_version_major__:
+    print('This script is intended to use with Python {}.'.format(
+        __exp_py_version_major__), file=sys.stderr)
+    print('You are using Python: {0}.{1}.{2}-{3}-{4}.'.format(
+        *sys.version_info) + '\n', file=sys.stderr)
+    sys.exit(1)
+
+if sys.version_info[1] < __min_py_version_minor__:
+    print('A minimal Python version of {maj}.{min} is necessary to execute this script.'.format(
+        maj=__exp_py_version_major__, min=__min_py_version_minor__), file=sys.stderr)
+    print('You are using Python: {0}.{1}.{2}-{3}-{4}.'.format(
+        *sys.version_info) + '\n', file=sys.stderr)
+    sys.exit(1)
+
+# Standard modules
+try:
+    from pathlib import Path
+except ImportError:
+    from pathlib2 import Path
+
+__author__ = 'Frank Brehm <frank.brehm@pixelpark.com>'
+__copyright__ = '(C) 2023 by Frank Brehm, Digitas Pixelpark GmbH, Berlin'
+
+# own modules:
+
+my_path = Path(__file__)
+my_real_path = my_path.resolve()
+bin_path = my_real_path.parent
+base_dir = bin_path.parent
+lib_dir = base_dir.joinpath('lib')
+module_dir = lib_dir.joinpath('pp_admintools')
+
+if module_dir.exists():
+    sys.path.insert(0, str(lib_dir))
+
+from pp_admintools.app.clean_empty_ldap_groups import CleanEmptyLdapGroupsApplication
+
+appname = os.path.basename(sys.argv[0])
+
+locale.setlocale(locale.LC_ALL, '')
+
+app = CleanEmptyLdapGroupsApplication(appname=appname, base_dir=base_dir)
+app.initialized = True
+
+if app.verbose > 2:
+    print('{c}-Object:\n{a}'.format(c=app.__class__.__name__, a=app))
+
+app()
+
+sys.exit(0)
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
diff --git a/lib/pp_admintools/app/clean_empty_ldap_groups.py b/lib/pp_admintools/app/clean_empty_ldap_groups.py
new file mode 100644 (file)
index 0000000..46faa10
--- /dev/null
@@ -0,0 +1,203 @@
+# -*- coding: utf-8 -*-
+"""
+@summary: An application module for removing empty groups from LDAP.
+
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2023 by Frank Brehm, Berlin
+"""
+from __future__ import absolute_import
+
+# Standard modules
+import copy
+import logging
+
+# Third party modules
+from fb_tools.collections import CIStringSet
+from fb_tools.common import is_sequence, to_bool
+from fb_tools.xlate import format_list
+
+from ldap3 import MODIFY_ADD, MODIFY_DELETE, MODIFY_REPLACE
+
+# Own modules
+from .ldap import BaseLdapApplication
+from .ldap import FatalLDAPError, LdapAppError
+from .. import pp
+from ..argparse_actions import LimitedIntegerOptionAction
+from ..xlate import XLATOR
+
+__version__ = '0.1.0'
+LOG = logging.getLogger(__name__)
+
+_ = XLATOR.gettext
+ngettext = XLATOR.ngettext
+
+
+# =============================================================================
+class CleanEmptyLdapGroupsUserError(LdapAppError):
+    """Special exception class for exceptions inside this module."""
+
+    pass
+
+
+# =============================================================================
+class CleanEmptyLdapGroupsApplication(BaseLdapApplication):
+    """Application class for removing empty groups from LDAP."""
+
+    show_simulate_option = True
+    show_quiet_option = False
+
+    default_max_cycles = 20
+    max_max_cycles = 1000
+
+    group_object_classes = ('posixGroup', 'groupOfNames', 'uniqueMember')
+    member_attributes = ('member', 'uniqueMember', 'memberUid', 'mgrpRFC822MailMember')
+
+    # -------------------------------------------------------------------------
+    def __init__(self, appname=None, base_dir=None):
+        """Initialize the CleanEmptyLdapGroupsApplication object."""
+        self.use_default_ldap_connection = False
+        self.use_multiple_ldap_connections = True
+        self.show_cmdline_ldap_timeout = True
+
+        self.dns_todo = CIStringSet()
+        self.dns_done = CIStringSet()
+
+        self.current_cycle = 0
+        self.last_nr_groups_done = 0
+        self._max_cycles = self.default_max_cycles
+
+        desc = _(
+            'Removes all LDAP groups, which does not have any members, that means, they are one '
+            'of the following objectClasses:')
+        desc += ' ' + format_list(self.group_object_classes) + ', '
+        desc += _('and they have none of the following attributes:')
+        desc += ' ' + format_list(self.member_attributes, do_repr=True)
+
+        super(CleanEmptyLdapGroupsApplication, self).__init__(
+            appname=appname, description=desc, base_dir=base_dir, initialized=False)
+
+        self.initialized = True
+
+    # -------------------------------------------
+    @property
+    def max_cycles(self):
+        """Define, that the given users will not be removed, bur deactivated instaed."""
+        return self._max_cycles
+
+    @max_cycles.setter
+    def max_cycles(self, value):
+        if value is None:
+            self._max_cycles = self.default_max_cycles
+            return
+        try:
+            v = int(value)
+            if v <= 0 or v > 1000:
+                msg = _(
+                    'The maximum number of cycles must not less or equal to zero and mast not be '
+                    'greater than {}.').format(self.max_max_cycles)
+                raise ValueError(msg)
+        except Exception as e:
+            msg = _('Got a {} for setting the maximum cycles:').format(e.__class__.__name__)
+            msg += ' ' + str(e)
+            raise CleanEmptyLdapGroupsUserError(msg)
+
+    # -------------------------------------------------------------------------
+    def as_dict(self, short=True):
+        """
+        Transform the elements of the object into a dict.
+
+        @param short: don't include local properties in resulting dict.
+        @type short: bool
+
+        @return: structure as dict
+        @rtype:  dict
+        """
+        res = super(CleanEmptyLdapGroupsApplication, self).as_dict(short=short)
+
+        res['default_max_cycles'] = self.default_max_cycles
+        res['group_object_classes'] = copy.copy(self.group_object_classes)
+        res['max_cycles'] = self.max_cycles
+        res['max_max_cycles'] = self.max_max_cycles
+        res['member_attributes'] = copy.copy(self.member_attributes)
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def init_arg_parser(self):
+        """Initialize specific command line parameters for this application."""
+        remove_group = self.arg_parser.add_argument_group(_('Removing options'))
+
+        cycles_help = _(
+            'The maximum number of iteration cycles for searching for empty groups. '
+            'It must not be less or equal to zero and must not be greater than {}.')
+        cycles_help = cycles_help.format(self.max_max_cycles)
+        cycles_help += ' ' + _('Default: {}.').format(self.default_max_cycles)
+
+        remove_group.add_argument(
+            '--cycles', dest='cycles', metavar=_('COUNT'), min_val=1, max_val=self.max_max_cycles,
+            action=LimitedIntegerOptionAction, help=cycles_help)
+
+        super(CleanEmptyLdapGroupsApplication, self).init_arg_parser()
+
+    # -------------------------------------------------------------------------
+    def _verify_instances(self):
+
+        super(CleanEmptyLdapGroupsApplication, self)._verify_instances(
+            is_admin=True, readonly=False)
+
+    # -------------------------------------------------------------------------
+    def post_init(self):
+        """Execute some steps before calling run()."""
+        super(CleanEmptyLdapGroupsApplication, self).post_init()
+
+        cycles = getattr(self.args, 'cycles', None)
+        if cycles:
+            self.max_cycles = cycles
+
+        self.check_instances()
+
+    # -------------------------------------------------------------------------
+    def check_instances(self):
+        """Check given instances for admin and read/write access."""
+        msg = _('Checking given instances for admin and read/write access.')
+        LOG.debug(msg)
+
+        all_ok = True
+
+        for inst_name in self.ldap_instances:
+            if inst_name not in self.cfg.ldap_connection:
+                msg = _('LDAP instance {!r} not found in configuration.').format(inst_name)
+                LOG.error(msg)
+                all_ok = False
+                continue
+
+            inst = self.cfg.ldap_connection[inst_name]
+
+            if inst.readonly:
+                msg = _('LDAP instance {!r} has only readonly access.').format(inst_name)
+                LOG.error(msg)
+                all_ok = False
+
+            if not inst.is_admin:
+                msg = _('No admin access to LDAP instance {!r}.').format(inst_name)
+                LOG.error(msg)
+                all_ok = False
+
+        if not all_ok:
+            self.exit(8)
+
+    # -------------------------------------------------------------------------
+    def _run(self):
+
+        LOG.debug(_('Searching for empty groups ...'))
+
+
+# =============================================================================
+if __name__ == '__main__':
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list