Skip to content

Commit dccdc60

Browse files
authored
Merge pull request #1842 from dbcli/RW/allow-leading-hyphen-on-cli-passwords
Allow CLI passwords starting with dash characters
2 parents 0d896ab + 620f126 commit dccdc60

5 files changed

Lines changed: 30 additions & 65 deletions

File tree

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Breaking Changes
1313
* Make `--batch` and `--execute` non-interactive by default.
1414
* Migrate `default_character_set` out of the `[main]` section of `~/.myclirc`.
1515
* Migrate `ssl_mode` out of the `[main]` section of `~/.myclirc`.
16+
* Limit `--password` without an argument to the final position.
1617

1718

1819
Features
@@ -23,6 +24,7 @@ Features
2324
Bug Fixes
2425
---------
2526
* Ensure that `--batch` and `--logfile` files are distinct.
27+
* Allow passwords defined at the CLI to start with dash.
2628

2729

2830
Documentation

mycli/main.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from io import TextIOWrapper
55
import os
66
import sys
7+
from textwrap import dedent
78
from typing import Callable
89

910
import click
@@ -78,9 +79,11 @@ class CliArgs:
7879
'--password',
7980
'password',
8081
type=INT_OR_STRING_CLICK_TYPE,
81-
is_flag=False,
82-
flag_value=EMPTY_PASSWORD_FLAG_SENTINEL,
83-
help='Prompt for (or pass in cleartext) the password to connect to the database.',
82+
help=dedent(
83+
"""Password to connect to the database.
84+
Use with a value to set the password at the CLI, or alone in the last position to request a prompt.
85+
"""
86+
),
8487
)
8588
password_file: str | None = clickdc.option(
8689
type=click.Path(),
@@ -376,7 +379,7 @@ def click_entrypoint(
376379
def main() -> int | None:
377380
try:
378381
result = click_entrypoint.main(
379-
filtered_sys_argv(),
382+
filtered_sys_argv(), # type: ignore[arg-type]
380383
standalone_mode=False, # disable builtin exception handling
381384
prog_name='mycli',
382385
)

mycli/packages/cli_utils.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
from __future__ import annotations
22

33
import sys
4+
from typing import Sequence
45

6+
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
7+
8+
9+
def filtered_sys_argv() -> Sequence[str | int]:
10+
args: Sequence[str | int] = sys.argv[1:]
11+
password_flag_forms = ['-p', '--pass', '--password']
512

6-
def filtered_sys_argv() -> list[str]:
7-
args = sys.argv[1:]
813
if args == ['-h']:
914
args = ['--help']
10-
return args
15+
16+
if args and args[-1] in password_flag_forms:
17+
args = list(args) + [EMPTY_PASSWORD_FLAG_SENTINEL]
18+
19+
return list(args)
1120

1221

1322
def is_valid_connection_scheme(text: str) -> tuple[bool, str | None]:

test/pytests/test_cli_utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import pytest
44

5+
from mycli.constants import EMPTY_PASSWORD_FLAG_SENTINEL
56
from mycli.packages import cli_utils
67
from mycli.packages.cli_utils import (
78
filtered_sys_argv,
@@ -22,6 +23,13 @@ def test_filtered_sys_argv(monkeypatch, argv, expected):
2223
assert filtered_sys_argv() == expected
2324

2425

26+
@pytest.mark.parametrize('password_flag', ['-p', '--pass', '--password'])
27+
def test_filtered_sys_argv_appends_empty_password_sentinel(monkeypatch, password_flag):
28+
monkeypatch.setattr(cli_utils.sys, 'argv', ['mycli', 'database', password_flag])
29+
30+
assert filtered_sys_argv() == ['database', password_flag, EMPTY_PASSWORD_FLAG_SENTINEL]
31+
32+
2533
@pytest.mark.parametrize(
2634
('text', 'is_valid', 'invalid_scheme'),
2735
[

test/pytests/test_main.py

Lines changed: 1 addition & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@
3636
DEFAULT_HOST,
3737
DEFAULT_PORT,
3838
DEFAULT_USER,
39+
EMPTY_PASSWORD_FLAG_SENTINEL,
3940
ER_MUST_CHANGE_PASSWORD_LOGIN,
4041
TEST_DATABASE,
4142
)
4243
from mycli.main import (
43-
EMPTY_PASSWORD_FLAG_SENTINEL,
4444
INT_OR_STRING_CLICK_TYPE,
4545
CliArgs,
4646
MyCli,
@@ -1199,63 +1199,6 @@ def run_query(self, query, new_line=True):
11991199
)
12001200

12011201

1202-
def test_password_flag_uses_sentinel(monkeypatch):
1203-
class Formatter:
1204-
format_name = None
1205-
1206-
class Logger:
1207-
def debug(self, *args, **args_dict):
1208-
pass
1209-
1210-
def warning(self, *args, **args_dict):
1211-
pass
1212-
1213-
class MockMyCli:
1214-
config = {
1215-
'main': {},
1216-
'alias_dsn': {},
1217-
'connection': {
1218-
'default_keepalive_ticks': 0,
1219-
},
1220-
}
1221-
1222-
def __init__(self, **_args):
1223-
self.logger = Logger()
1224-
self.destructive_warning = False
1225-
self.main_formatter = Formatter()
1226-
self.redirect_formatter = Formatter()
1227-
self.ssl_mode = 'auto'
1228-
self.default_keepalive_ticks = 0
1229-
1230-
def connect(self, **args):
1231-
MockMyCli.connect_args = args
1232-
1233-
def run_query(self, query, new_line=True):
1234-
pass
1235-
1236-
import mycli.main
1237-
1238-
monkeypatch.setattr(mycli.main, 'MyCli', MockMyCli)
1239-
runner = CliRunner()
1240-
1241-
result = runner.invoke(
1242-
mycli.main.click_entrypoint,
1243-
args=[
1244-
'--user',
1245-
'user',
1246-
'--host',
1247-
DEFAULT_HOST,
1248-
'--port',
1249-
f'{DEFAULT_PORT}',
1250-
'--database',
1251-
'database',
1252-
'--password',
1253-
],
1254-
)
1255-
assert result.exit_code == 0, result.output + ' ' + str(result.exception)
1256-
assert MockMyCli.connect_args['passwd'] == EMPTY_PASSWORD_FLAG_SENTINEL
1257-
1258-
12591202
def test_password_option_uses_cleartext_value(monkeypatch):
12601203
class Formatter:
12611204
format_name = None

0 commit comments

Comments
 (0)