From 208111a3af4d9d3ea7c1854546099234ba483e6c Mon Sep 17 00:00:00 2001 From: William Brown Date: Mar 14 2019 23:28:54 +0000 Subject: Ticket 49715 - extend account functionality Bug Description: It was noted by mreynolds that account doesn't do as much as user does. This brings account to partial-feature parity with user, able to modify, show and delete accounts. Fix Description: Add the ability to show, modify and delete generic account types. Note that account can never, and will never gain the ability to create accounts, because "accounts" are such an opinionated and complex topic. For creating accounts, user will remain the preferred command. Account exists to "manage existing" account types, that an external system may create or feed to the 389 instance. https://pagure.io/389-ds-base/issue/49715 Author: William Brown Review by: spichugi (Thanks) --- diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 95b40cf..b1bd38b 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -56,6 +56,8 @@ def _gen_not(extra=None): def _gen_filter(attrtypes, values, extra=None): filt = '' + if attrtypes is None: + raise ValueError("Attempting to filter on type that doesn't support filtering!") for attr, value in zip(attrtypes, values): if attr is not None and value is not None: filt += '(%s=%s)' % (attr, ldap_filter.escape_filter_chars(value)) diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py index d94e4e4..636e7f5 100644 --- a/src/lib389/lib389/cli_base/__init__.py +++ b/src/lib389/lib389/cli_base/__init__.py @@ -297,22 +297,37 @@ def _generic_modify_change_to_mod(change): raise ValueError("Unknown action '%s'. Expected add, delete, replace" % change) +def _generic_modify_inner(log, o, changes): + # Now parse the series of arguments. + # Turn them into mod lists. See apply_mods. + mods = [_generic_modify_change_to_mod(x) for x in changes] + log.debug("Requested mods: %s" % mods) + # Now push them to dsldapobject to modify + o.apply_mods(mods) + print('Successfully modified %s' % o.dn) + + def _generic_modify(inst, basedn, log, manager_class, selector, args=None): + if not args or not args.changes: + raise ValueError("Missing modify actions to perform.") # Here, we should have already selected the type etc. mc should be a - # type of DSLdapObject (singular) + # type of DSLdapObjects (plural) mc = manager_class(inst, basedn) - # Get the object - if args and args.changes: - o = mc.get(selector) - # Now parse the series of arguments. - # Turn them into mod lists. See apply_mods. - mods = [_generic_modify_change_to_mod(x) for x in args.changes] - log.debug("Requested mods: %s" % mods) - # Now push them to dsldapobject to modify - o.apply_mods(mods) - print('Successfully modified %s' % o.dn) - else: + # Get the object singular by selector + o = mc.get(selector) + _generic_modify_inner(log, o, args.changes) + + +def _generic_modify_dn(inst, basedn, log, manager_class, dn, args=None): + if not args or not args.changes: raise ValueError("Missing modify actions to perform.") + # Here, we should have already selected the type etc. mc should be a + # type of DSLdapObjects (plural) + mc = manager_class(inst, basedn) + # Get the object singular by dn + o = mc.get(dn=dn) + _generic_modify_inner(log, o, args.changes) + class LogCapture(logging.Handler): diff --git a/src/lib389/lib389/cli_idm/account.py b/src/lib389/lib389/cli_idm/account.py index 610ae7e..f95c6c6 100644 --- a/src/lib389/lib389/cli_idm/account.py +++ b/src/lib389/lib389/cli_idm/account.py @@ -11,15 +11,35 @@ import argparse from lib389.idm.account import Account, Accounts from lib389.cli_base import ( + _generic_get, + _generic_get_dn, _generic_list, + _generic_delete, + _generic_modify_dn, _get_arg, + _warn, ) MANY = Accounts +SINGULAR = Account def list(inst, basedn, log, args): _generic_list(inst, basedn, log.getChild('_generic_list'), MANY, args) +def get_dn(inst, basedn, log, args): + dn = _get_arg( args.dn, msg="Enter dn to retrieve") + _generic_get_dn(inst, basedn, log.getChild('_generic_get_dn'), MANY, dn, args) + +def delete(inst, basedn, log, args, warn=True): + dn = _get_arg( args.dn, msg="Enter dn to delete") + if warn: + _warn(dn, msg="Deleting %s %s" % (SINGULAR.__name__, dn)) + _generic_delete(inst, basedn, log.getChild('_generic_delete'), SINGULAR, dn, args) + +def modify(inst, basedn, log, args, warn=True): + dn = _get_arg( args.dn, msg="Enter dn to modify") + _generic_modify_dn(inst, basedn, log.getChild('_generic_modify'), MANY, dn, args) + def status(inst, basedn, log, args): dn = _get_arg( args.dn, msg="Enter dn to check") accounts = Accounts(inst, basedn) @@ -64,13 +84,27 @@ def change_password(inst, basedn, log, args): def create_parser(subparsers): - account_parser = subparsers.add_parser('account', help='Manage generic accounts IE account locking and unlocking.') + account_parser = subparsers.add_parser('account', help='''Manage generic accounts, with tasks +like modify, locking and unlocking. To create an account, see "user" subcommand instead.''') subcommands = account_parser.add_subparsers(help='action') - list_parser = subcommands.add_parser('list', help='list') + list_parser = subcommands.add_parser('list', help='list accounts that could login to the directory') list_parser.set_defaults(func=list) + get_dn_parser = subcommands.add_parser('get-by-dn', help='get-by-dn ') + get_dn_parser.set_defaults(func=get_dn) + get_dn_parser.add_argument('dn', nargs='?', help='The dn to get and display') + + modify_dn_parser = subcommands.add_parser('modify-by-dn', help='modify-by-dn :: ...') + modify_dn_parser.set_defaults(func=modify) + modify_dn_parser.add_argument('dn', nargs=1, help='The dn to get and display') + modify_dn_parser.add_argument('changes', nargs='+', help="A list of changes to apply in format: ::") + + delete_parser = subcommands.add_parser('delete', help='deletes the account') + delete_parser.set_defaults(func=delete) + delete_parser.add_argument('dn', nargs='?', help='The dn of the account to delete') + lock_parser = subcommands.add_parser('lock', help='lock') lock_parser.set_defaults(func=lock) lock_parser.add_argument('dn', nargs='?', help='The dn to lock') diff --git a/src/lib389/lib389/idm/account.py b/src/lib389/lib389/idm/account.py index 729f0b4..4bd6f6b 100644 --- a/src/lib389/lib389/idm/account.py +++ b/src/lib389/lib389/idm/account.py @@ -169,7 +169,7 @@ class Accounts(DSLdapObjects): 'posixGroup', 'mailRecipient', ] - # MUST BE NONE. + # MUST BE NONE. For more, see _gen_filter in _mapped_object.py. self._filterattrs = None self._childobject = Account self._basedn = basedn @@ -181,7 +181,7 @@ class Accounts(DSLdapObjects): _gen_filter(_term_gen('objectclass'), self._objectclasses) ) - + class Anonymous(DSLdapObject): """A single instance of Anonymous bind