Skip to content

Commit

Permalink
Issue 5971 - CLI - Fix password prompt for repl status
Browse files Browse the repository at this point in the history
Description: dsconf replication status is failing with 'Invalid credentials'
when the password of the Directory Manager is not identical on servers.
Ask for each instance's password separently.
Expand the help message in CLI for replication and agreement status commands.

Fixes: #5971

Reviewed by: ?
  • Loading branch information
droideck committed Oct 31, 2023
1 parent 91d1df6 commit 2020477
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 31 deletions.
13 changes: 12 additions & 1 deletion src/lib389/lib389/agreement.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import datetime
from lib389._constants import *
from lib389.properties import *
from lib389.cli_base import _get_arg
from lib389._entry import FormatDict
from lib389.utils import normalizeDN, ensure_bytes, ensure_str, ensure_dict_str, ensure_list_str
from lib389 import Entry, DirSrv, NoSuchEntryError, InvalidArgumentError
Expand Down Expand Up @@ -305,7 +306,7 @@ def get_lag_time(self, suffix, agmt_name, binddn=None, bindpw=None):
# Return a nice formated timestamp
return "{:0>8}".format(str(lag))

def status(self, winsync=False, just_status=False, use_json=False, binddn=None, bindpw=None):
def status(self, winsync=False, just_status=False, use_json=False, binddn=None, bindpw=None, pwprompt=False):
"""Get the status of a replication agreement
:param winsync: Specifies if the the agreement is a winsync replication agreement
:type winsync: boolean
Expand All @@ -317,6 +318,8 @@ def status(self, winsync=False, just_status=False, use_json=False, binddn=None,
:type binddn: str
:param bindpw: Password for the bind DN
:type bindpw: str
:param pwprompt: If binddn or bindpw is None, ask for them interactively
:type pwprompt: boolean
:returns: A status message
:raises: ValueError - if failing to get agmt status
"""
Expand All @@ -328,6 +331,14 @@ def status(self, winsync=False, just_status=False, use_json=False, binddn=None,
# RUV entry under the suffix, then we can't get the status. So in this case we
# need to provide a DN and password.
if not winsync:
if pwprompt:
host = self.get_attr_val_utf8(AGMT_HOST)
port = self.get_attr_val_utf8(AGMT_PORT)
suffix = self.get_attr_val_utf8(REPL_ROOT)
if binddn is None:
binddn = _get_arg(None, msg=f"Enter bind DN for the replicated suffix ({suffix}) on {host}:{port}")
if bindpw is None:
bindpw = _get_arg(None, msg=f"Enter password for ({binddn}) to the replicated suffix ({suffix}) on {host}:{port}", hidden=True)
try:
status = self.get_agmt_status(binddn=binddn, bindpw=bindpw)
except ldap.INVALID_CREDENTIALS as e:
Expand Down
54 changes: 26 additions & 28 deletions src/lib389/lib389/cli_conf/replication.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,13 +317,12 @@ def list_suffixes(inst, basedn, log, args):
def get_repl_status(inst, basedn, log, args):
replicas = Replicas(inst)
replica = replicas.get(args.suffix)
pw_and_dn_prompt = False
if args.bind_passwd_file is not None:
passwd = get_passwd_from_file(args.bind_passwd_file)
elif args.bind_passwd_prompt:
passwd = _get_arg(None, msg=f"Enter password for ({args.bind_dn})", hidden=True, confirm=True)
else:
passwd = args.bind_passwd
status = replica.status(binddn=args.bind_dn, bindpw=passwd)
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
pw_and_dn_prompt = True
status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
if args.json:
log.info(json.dumps({"type": "list", "items": status}, indent=4))
else:
Expand All @@ -334,14 +333,12 @@ def get_repl_status(inst, basedn, log, args):
def get_repl_winsync_status(inst, basedn, log, args):
replicas = Replicas(inst)
replica = replicas.get(args.suffix)
pw_and_dn_prompt = False
if args.bind_passwd_file is not None:
passwd = get_passwd_from_file(args.bind_passwd_file)
elif args.bind_passwd_prompt:
passwd = _get_arg(None, msg=f"Enter password for ({args.bind_dn})", hidden=True, confirm=True)
else:
passwd = args.bind_passwd

status = replica.status(binddn=args.bind_dn, bindpw=passwd, winsync=True)
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
pw_and_dn_prompt = True
status = replica.status(binddn=args.bind_dn, bindpw=args.bind_passwd, winsync=True, pwprompt=pw_and_dn_prompt)
if args.json:
log.info(json.dumps({"type": "list", "items": status}, indent=4))
else:
Expand Down Expand Up @@ -927,11 +924,12 @@ def poke_agmt(inst, basedn, log, args):

def get_agmt_status(inst, basedn, log, args):
agmt = get_agmt(inst, args)
pw_and_dn_prompt = False
if args.bind_passwd_file is not None:
args.bind_passwd = get_passwd_from_file(args.bind_passwd_file)
if (args.bind_dn is not None and args.bind_passwd is None) or args.bind_passwd_prompt:
args.bind_passwd = _get_arg(None, msg=f"Enter password for \"{args.bind_dn}\"", hidden=True, confirm=True)
status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd)
if args.bind_passwd_prompt or args.bind_dn is None or args.bind_passwd is None:
pw_and_dn_prompt = True
status = agmt.status(use_json=args.json, binddn=args.bind_dn, bindpw=args.bind_passwd, pwprompt=pw_and_dn_prompt)
log.info(status)


Expand Down Expand Up @@ -1321,19 +1319,19 @@ def create_parser(subparsers):
repl_status_parser = repl_subcommands.add_parser('status', help='Display the current status of all the replication agreements')
repl_status_parser.set_defaults(func=get_repl_status)
repl_status_parser.add_argument('--suffix', required=True, help="Sets the DN of the replication suffix")
repl_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer")
repl_status_parser.add_argument('--bind-passwd', help="Sets the password for the bind DN")
repl_status_parser.add_argument('--bind-passwd-file', help="File containing the password")
repl_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for password")
repl_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer. If not set, current instance's root DN will be used. It will be used for all agreements")
repl_status_parser.add_argument('--bind-passwd', help="Sets the password for the bind DN. It will be used for all agreements")
repl_status_parser.add_argument('--bind-passwd-file', help="File containing the password. It will be used for all agreements")
repl_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for passwords for each agreement's instance separately")

repl_winsync_status_parser = repl_subcommands.add_parser('winsync-status', help='Display the current status of all '
'the replication agreements')
repl_winsync_status_parser.set_defaults(func=get_repl_winsync_status)
repl_winsync_status_parser.add_argument('--suffix', required=True, help="Sets the DN of the replication suffix")
repl_winsync_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer")
repl_winsync_status_parser.add_argument('--bind-passwd', help="Sets the password of the bind DN")
repl_winsync_status_parser.add_argument('--bind-passwd-file', help="File containing the password")
repl_winsync_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for password")
repl_winsync_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer. Currectly not used")
repl_winsync_status_parser.add_argument('--bind-passwd', help="Sets the password of the bind DN. Currectly not used")
repl_winsync_status_parser.add_argument('--bind-passwd-file', help="File containing the password. Currectly not used")
repl_winsync_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for password. Currectly not used")

repl_promote_parser = repl_subcommands.add_parser('promote', help='Promote a replica to a hub or supplier')
repl_promote_parser.set_defaults(func=promote_replica)
Expand Down Expand Up @@ -1510,10 +1508,10 @@ def create_parser(subparsers):
agmt_status_parser.set_defaults(func=get_agmt_status)
agmt_status_parser.add_argument('AGMT_NAME', nargs=1, help='The name of the replication agreement')
agmt_status_parser.add_argument('--suffix', required=True, help="Sets the DN of the replication suffix")
agmt_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer")
agmt_status_parser.add_argument('--bind-passwd', help="Sets the password for the bind DN")
agmt_status_parser.add_argument('--bind-passwd-file', help="File containing the password")
agmt_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for password")
agmt_status_parser.add_argument('--bind-dn', help="Sets the DN to use to authenticate to the consumer. If not set, current instance's root DN will be used. It will be used for all agreements")
agmt_status_parser.add_argument('--bind-passwd', help="Sets the password for the bind DN. It will be used for all agreements")
agmt_status_parser.add_argument('--bind-passwd-file', help="File containing the password. It will be used for all agreements")
agmt_status_parser.add_argument('--bind-passwd-prompt', action='store_true', help="Prompt for passwords for each agreement's instance separately")

# Delete
agmt_del_parser = agmt_subcommands.add_parser('delete', help='Delete replication agreement')
Expand Down
4 changes: 2 additions & 2 deletions src/lib389/lib389/replica.py
Original file line number Diff line number Diff line change
Expand Up @@ -1734,13 +1734,13 @@ def get_suffix(self):

return self._suffix

def status(self, binddn=None, bindpw=None, winsync=False):
def status(self, binddn=None, bindpw=None, winsync=False, pwprompt=False):
"""Get a list of the status for every agreement
"""
agmtList = []
agmts = Agreements(self._instance, self.dn, winsync=winsync).list()
for agmt in agmts:
raw_status = agmt.status(binddn=binddn, bindpw=bindpw, use_json=True, winsync=winsync)
raw_status = agmt.status(binddn=binddn, bindpw=bindpw, use_json=True, winsync=winsync, pwprompt=pwprompt)
agmtList.append(json.loads(raw_status))

# sort the list of agreements by the lag time
Expand Down

0 comments on commit 2020477

Please sign in to comment.