From 7d534efdcd96b13524dae587c3c5994ed01924ab Mon Sep 17 00:00:00 2001 From: Simon Pichugin Date: Fri, 16 Feb 2024 13:52:36 -0800 Subject: [PATCH] Issue 6067 - Improve dsidm CLI No Such Entry handling (#6079) Description: Add additional error processing to dsidm CLI tool for when basedn or OU subentries are absent. Related: https://github.com/389ds/389-ds-base/issues/6067 Reviewed by: @vashirov (Thanks!) --- src/lib389/cli/dsidm | 21 ++++++++------- src/lib389/lib389/cli_idm/__init__.py | 38 ++++++++++++++++++++++++++- src/lib389/lib389/cli_idm/account.py | 4 +-- src/lib389/lib389/cli_idm/service.py | 4 ++- src/lib389/lib389/idm/group.py | 10 ++++--- src/lib389/lib389/idm/posixgroup.py | 5 ++-- src/lib389/lib389/idm/services.py | 5 ++-- src/lib389/lib389/idm/user.py | 5 ++-- 8 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm index 1b739b103..970973f4f 100755 --- a/src/lib389/cli/dsidm +++ b/src/lib389/cli/dsidm @@ -2,7 +2,7 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2023 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -19,6 +19,7 @@ import argparse import argcomplete from lib389.utils import get_instance_list, instance_choices from lib389._constants import DSRC_HOME +from lib389.cli_idm import _get_basedn_arg from lib389.cli_idm import account as cli_account from lib389.cli_idm import initialise as cli_init from lib389.cli_idm import organizationalunit as cli_ou @@ -117,14 +118,6 @@ if __name__ == '__main__': parser.print_help() sys.exit(1) - if dsrc_inst['basedn'] is None: - errmsg = "Must provide a basedn!" - if args.json: - sys.stderr.write('{"desc": "%s"}\n' % errmsg) - else: - log.error(errmsg) - sys.exit(1) - if not args.verbose: signal.signal(signal.SIGINT, signal_handler) @@ -135,7 +128,15 @@ if __name__ == '__main__': result = False try: inst = connect_instance(dsrc_inst=dsrc_inst, verbose=args.verbose, args=args) - result = args.func(inst, dsrc_inst['basedn'], log, args) + basedn = _get_basedn_arg(inst, args, log, msg="Enter basedn") + if basedn is None: + errmsg = "Must provide a basedn!" + if args.json: + sys.stderr.write('{"desc": "%s"}\n' % errmsg) + else: + log.error(errmsg) + sys.exit(1) + result = args.func(inst, basedn, log, args) if args.verbose: log.info("Command successful.") except Exception as e: diff --git a/src/lib389/lib389/cli_idm/__init__.py b/src/lib389/lib389/cli_idm/__init__.py index 0dab54847..e3622246d 100644 --- a/src/lib389/lib389/cli_idm/__init__.py +++ b/src/lib389/lib389/cli_idm/__init__.py @@ -1,15 +1,30 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2023 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). # See LICENSE for details. # --- END COPYRIGHT BLOCK --- +import sys import ldap from getpass import getpass import json +from lib389._mapped_object import DSLdapObject +from lib389.cli_base import _get_dn_arg +from lib389.idm.user import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_USER +from lib389.idm.group import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_GROUP +from lib389.idm.posixgroup import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_POSIXGROUP +from lib389.idm.services import DEFAULT_BASEDN_RDN as DEFAULT_BASEDN_RDN_SERVICES + +# The key is module name, the value is default RDN +BASEDN_RDNS = { + 'user': DEFAULT_BASEDN_RDN_USER, + 'group': DEFAULT_BASEDN_RDN_GROUP, + 'posixgroup': DEFAULT_BASEDN_RDN_POSIXGROUP, + 'service': DEFAULT_BASEDN_RDN_SERVICES, +} def _get_arg(args, msg=None): @@ -37,6 +52,27 @@ def _get_args(args, kws): return kwargs +def _get_basedn_arg(inst, args, log, msg=None): + basedn_arg = _get_dn_arg(args.basedn, msg="Enter basedn") + if not DSLdapObject(inst, basedn_arg).exists(): + raise ValueError(f'The base DN "{basedn_arg}" does not exist.') + + # Get the RDN based on the last part of the module name if applicable + # (lib389.cli_idm.user -> user) + try: + command_name = args.func.__module__.split('.')[-1] + object_rdn = BASEDN_RDNS[command_name] + # Check if the DN for our command exists + command_basedn = f'{object_rdn},{basedn_arg}' + if not DSLdapObject(inst, command_basedn).exists(): + errmsg = f'The DN "{command_basedn}" does not exist.' + errmsg += f' It is required for "{command_name}" subcommand. Please create it first.' + raise ValueError(errmsg) + except KeyError: + pass + return basedn_arg + + # This is really similar to get_args, but generates from an array def _get_attributes(args, attrs): kwargs = {} diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py index 5d7b9cc77..15f766588 100644 --- a/src/lib389/lib389/cli_idm/account.py +++ b/src/lib389/lib389/cli_idm/account.py @@ -1,5 +1,5 @@ # --- BEGIN COPYRIGHT BLOCK --- -# Copyright (C) 2023, Red Hat inc, +# Copyright (C) 2024, Red Hat inc, # Copyright (C) 2018, William Brown # All rights reserved. # @@ -91,7 +91,6 @@ def entry_status(inst, basedn, log, args): def subtree_status(inst, basedn, log, args): - basedn = _get_dn_arg(args.basedn, msg="Enter basedn to check") filter = "" scope = ldap.SCOPE_SUBTREE epoch_inactive_time = None @@ -121,7 +120,6 @@ def subtree_status(inst, basedn, log, args): def bulk_update(inst, basedn, log, args): - basedn = _get_dn_arg(args.basedn, msg="Enter basedn to search") search_filter = "(objectclass=*)" scope = ldap.SCOPE_SUBTREE scope_str = "sub" diff --git a/src/lib389/lib389/cli_idm/service.py b/src/lib389/lib389/cli_idm/service.py index c62fc12d1..c2b2c8c84 100644 --- a/src/lib389/lib389/cli_idm/service.py +++ b/src/lib389/lib389/cli_idm/service.py @@ -57,7 +57,9 @@ def rename(inst, basedn, log, args, warn=True): _generic_rename(inst, basedn, log.getChild('_generic_rename'), MANY, rdn, args) def create_parser(subparsers): - service_parser = subparsers.add_parser('service', help='Manage service accounts', formatter_class=CustomHelpFormatter) + service_parser = subparsers.add_parser('service', + help='Manage service accounts. The organizationalUnit (by default "ou=Services") ' + 'needs to exist prior to managing service accounts.', formatter_class=CustomHelpFormatter) subcommands = service_parser.add_subparsers(help='action') diff --git a/src/lib389/lib389/idm/group.py b/src/lib389/lib389/idm/group.py index 1b60a1f51..2cf2c7b23 100644 --- a/src/lib389/lib389/idm/group.py +++ b/src/lib389/lib389/idm/group.py @@ -1,6 +1,6 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2023 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -16,6 +16,8 @@ MUST_ATTRIBUTES = [ 'cn', ] RDN = 'cn' +DEFAULT_BASEDN_RDN = 'ou=Groups' +DEFAULT_BASEDN_RDN_ADMIN_GROUPS = 'ou=People' class Group(DSLdapObject): @@ -93,7 +95,7 @@ class Groups(DSLdapObjects): :type basedn: str """ - def __init__(self, instance, basedn, rdn='ou=Groups'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN): super(Groups, self).__init__(instance) self._objectclasses = [ 'groupOfNames', @@ -140,7 +142,7 @@ class UniqueGroup(DSLdapObject): class UniqueGroups(DSLdapObjects): # WARNING!!! # Use group, not unique group!!! - def __init__(self, instance, basedn, rdn='ou=Groups'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN): super(UniqueGroups, self).__init__(instance) self._objectclasses = [ 'groupOfUniqueNames', @@ -203,7 +205,7 @@ class nsAdminGroups(DSLdapObjects): :type rdn: str """ - def __init__(self, instance, basedn, rdn='ou=People'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN_ADMIN_GROUPS): super(nsAdminGroups, self).__init__(instance) self._objectclasses = [ 'nsAdminGroup' diff --git a/src/lib389/lib389/idm/posixgroup.py b/src/lib389/lib389/idm/posixgroup.py index d1debcf12..45735c579 100644 --- a/src/lib389/lib389/idm/posixgroup.py +++ b/src/lib389/lib389/idm/posixgroup.py @@ -1,6 +1,6 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2023 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -17,6 +17,7 @@ MUST_ATTRIBUTES = [ 'gidNumber', ] RDN = 'cn' +DEFAULT_BASEDN_RDN = 'ou=Groups' class PosixGroup(DSLdapObject): @@ -72,7 +73,7 @@ class PosixGroups(DSLdapObjects): :type basedn: str """ - def __init__(self, instance, basedn, rdn='ou=Groups'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN): super(PosixGroups, self).__init__(instance) self._objectclasses = [ 'groupOfNames', diff --git a/src/lib389/lib389/idm/services.py b/src/lib389/lib389/idm/services.py index d1e5b4693..e750a32c4 100644 --- a/src/lib389/lib389/idm/services.py +++ b/src/lib389/lib389/idm/services.py @@ -1,6 +1,6 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2021 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -16,6 +16,7 @@ RDN = 'cn' MUST_ATTRIBUTES = [ 'cn', ] +DEFAULT_BASEDN_RDN = 'ou=Services' class ServiceAccount(Account): """A single instance of Service entry @@ -59,7 +60,7 @@ class ServiceAccounts(DSLdapObjects): :type basedn: str """ - def __init__(self, instance, basedn, rdn='ou=Services'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN): super(ServiceAccounts, self).__init__(instance) self._objectclasses = [ 'applicationProcess', diff --git a/src/lib389/lib389/idm/user.py b/src/lib389/lib389/idm/user.py index 1206a6e08..3b21ccf1c 100644 --- a/src/lib389/lib389/idm/user.py +++ b/src/lib389/lib389/idm/user.py @@ -1,6 +1,6 @@ # --- BEGIN COPYRIGHT BLOCK --- # Copyright (C) 2016, William Brown -# Copyright (C) 2023 Red Hat, Inc. +# Copyright (C) 2024 Red Hat, Inc. # All rights reserved. # # License: GPL (version 3 or any later version). @@ -23,6 +23,7 @@ MUST_ATTRIBUTES = [ 'homeDirectory', ] RDN = 'uid' +DEFAULT_BASEDN_RDN = 'ou=People' TEST_USER_PROPERTIES = { 'uid': 'testuser', @@ -201,7 +202,7 @@ class UserAccounts(DSLdapObjects): :type rdn: str """ - def __init__(self, instance, basedn, rdn='ou=People'): + def __init__(self, instance, basedn, rdn=DEFAULT_BASEDN_RDN): super(UserAccounts, self).__init__(instance) self._objectclasses = [ 'account', -- 2.48.1