Marco Ricci commited on 2024-10-15 12:47:49
Zeige 2 geänderte Dateien mit 71 Einfügungen und 0 Löschungen.
The `derivepassphrase` command-line largely, and vault(1) completely, treats an empty service name the same as if no service name had been supplied, switching over to global operation instead of service-specific operation (or erroring out in case a service must be given). For `derivepassphrase`, this is mostly a user interface issue – the underlying machinery supports empty service names –, but kept for compatibility with vault(1). However, this is very easy to diagnose, and the user would benefit from seeing a warning about a seemingly omitted service name. So do that, and issue a warning upon encountering an empty service name on the command-line or in an imported configuration. (The warning message changes slightly in each case.) Also, add explicit tests for these two scenarios that trigger the warning.
| ... | ... |
@@ -1358,6 +1358,17 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915 |
| 1358 | 1358 |
msg = f'{opt_str} does not take a SERVICE argument'
|
| 1359 | 1359 |
raise click.UsageError(msg) |
| 1360 | 1360 |
|
| 1361 |
+ if service == '': # noqa: PLC1901 |
|
| 1362 |
+ click.echo( |
|
| 1363 |
+ ( |
|
| 1364 |
+ f'{PROG_NAME}: Warning: An empty SERVICE is not '
|
|
| 1365 |
+ f'supported by vault(1). For compatibility, this will be ' |
|
| 1366 |
+ f'treated as if SERVICE was not supplied, i.e., it will ' |
|
| 1367 |
+ f'error out, or operate on global settings.' |
|
| 1368 |
+ ), |
|
| 1369 |
+ err=True, |
|
| 1370 |
+ ) |
|
| 1371 |
+ |
|
| 1361 | 1372 |
if edit_notes: |
| 1362 | 1373 |
assert service is not None |
| 1363 | 1374 |
configuration = get_config() |
| ... | ... |
@@ -1429,6 +1440,15 @@ def derivepassphrase_vault( # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915 |
| 1429 | 1440 |
f'{json.dumps(step.old_value)}.'
|
| 1430 | 1441 |
) |
| 1431 | 1442 |
click.echo(err_msg, err=True) |
| 1443 |
+ if '' in maybe_config['services']: |
|
| 1444 |
+ err_msg = ( |
|
| 1445 |
+ f'{PROG_NAME}: Warning: An empty SERVICE is not '
|
|
| 1446 |
+ f'supported by vault(1), and the empty-string service ' |
|
| 1447 |
+ f'settings will be inaccessible and ineffective. ' |
|
| 1448 |
+ f'To ensure that vault(1) and {PROG_NAME} see the settings, '
|
|
| 1449 |
+ f'move them into the "global" section.' |
|
| 1450 |
+ ) |
|
| 1451 |
+ click.echo(err_msg, err=True) |
|
| 1432 | 1452 |
form = cast( |
| 1433 | 1453 |
Literal['NFC', 'NFD', 'NFKC', 'NFKD'], |
| 1434 | 1454 |
maybe_config.get('global', {}).get(
|
| ... | ... |
@@ -621,6 +621,57 @@ class TestCLI: |
| 621 | 621 |
result = tests.ReadableResult.parse(_result) |
| 622 | 622 |
assert result.clean_exit(empty_stderr=True), 'expected clean exit' |
| 623 | 623 |
|
| 624 |
+ def test_211a_empty_service_name_causes_warning( |
|
| 625 |
+ self, |
|
| 626 |
+ monkeypatch: pytest.MonkeyPatch, |
|
| 627 |
+ ) -> None: |
|
| 628 |
+ def expected_warning_line(line: str) -> bool: |
|
| 629 |
+ return is_harmless_config_import_warning_line(line) or ( |
|
| 630 |
+ ' Warning: An empty SERVICE is not supported by vault(1)' |
|
| 631 |
+ in line |
|
| 632 |
+ ) |
|
| 633 |
+ |
|
| 634 |
+ monkeypatch.setattr(cli, '_prompt_for_passphrase', tests.auto_prompt) |
|
| 635 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 636 |
+ with tests.isolated_vault_config( |
|
| 637 |
+ monkeypatch=monkeypatch, |
|
| 638 |
+ runner=runner, |
|
| 639 |
+ config={'services': {}},
|
|
| 640 |
+ ): |
|
| 641 |
+ _result = runner.invoke( |
|
| 642 |
+ cli.derivepassphrase_vault, |
|
| 643 |
+ ['--config', '--length=30', '--', ''], |
|
| 644 |
+ catch_exceptions=False, |
|
| 645 |
+ ) |
|
| 646 |
+ result = tests.ReadableResult.parse(_result) |
|
| 647 |
+ assert result.clean_exit(empty_stderr=False), 'expected clean exit' |
|
| 648 |
+ assert result.stderr is not None, 'expected known error output' |
|
| 649 |
+ assert all( |
|
| 650 |
+ expected_warning_line(line) |
|
| 651 |
+ for line in result.stderr.splitlines(False) |
|
| 652 |
+ ), 'expected known error output' |
|
| 653 |
+ assert cli._load_config() == {
|
|
| 654 |
+ 'global': {'length': 30},
|
|
| 655 |
+ 'services': {},
|
|
| 656 |
+ }, 'requested configuration change was not applied' |
|
| 657 |
+ _result = runner.invoke( |
|
| 658 |
+ cli.derivepassphrase_vault, |
|
| 659 |
+ ['--import', '-'], |
|
| 660 |
+ input=json.dumps({'services': {'': {'length': 40}}}),
|
|
| 661 |
+ catch_exceptions=False, |
|
| 662 |
+ ) |
|
| 663 |
+ result = tests.ReadableResult.parse(_result) |
|
| 664 |
+ assert result.clean_exit(empty_stderr=False), 'expected clean exit' |
|
| 665 |
+ assert result.stderr is not None, 'expected known error output' |
|
| 666 |
+ assert all( |
|
| 667 |
+ expected_warning_line(line) |
|
| 668 |
+ for line in result.stderr.splitlines(False) |
|
| 669 |
+ ), 'expected known error output' |
|
| 670 |
+ assert cli._load_config() == {
|
|
| 671 |
+ 'global': {'length': 30},
|
|
| 672 |
+ 'services': {'': {'length': 40}},
|
|
| 673 |
+ }, 'requested configuration change was not applied' |
|
| 674 |
+ |
|
| 624 | 675 |
@pytest.mark.parametrize( |
| 625 | 676 |
['options', 'service'], |
| 626 | 677 |
[ |
| 627 | 678 |