Marco Ricci commited on 2024-08-31 13:32:33
Zeige 7 geänderte Dateien mit 415 Einfügungen und 410 Löschungen.
Also replicate this module structure in the tests, which allows us to use `pytest.importorskip` to deal with missing `cryptography` support.
| ... | ... |
@@ -2,33 +2,18 @@ |
| 2 | 2 |
# |
| 3 | 3 |
# SPDX-License-Identifier: MIT |
| 4 | 4 |
|
| 5 |
-"""Command-line interface for derivepassphrase_export.""" |
|
| 5 |
+"""Foreign configuration exporter for derivepassphrase.""" |
|
| 6 | 6 |
|
| 7 | 7 |
from __future__ import annotations |
| 8 | 8 |
|
| 9 |
-import base64 |
|
| 10 |
-import importlib |
|
| 11 |
-import json |
|
| 12 |
-import logging |
|
| 13 | 9 |
import os |
| 14 |
-from typing import TYPE_CHECKING, Any, Literal |
|
| 15 |
- |
|
| 16 |
-import click |
|
| 17 |
-from typing_extensions import assert_never |
|
| 18 | 10 |
|
| 19 | 11 |
import derivepassphrase as dpp |
| 20 |
-from derivepassphrase import _types |
|
| 21 |
- |
|
| 22 |
-if TYPE_CHECKING: |
|
| 23 |
- import types |
|
| 24 |
- from collections.abc import Sequence |
|
| 25 | 12 |
|
| 26 | 13 |
__author__ = dpp.__author__ |
| 27 | 14 |
__version__ = dpp.__version__ |
| 28 | 15 |
|
| 29 |
-__all__ = ('derivepassphrase_export',)
|
|
| 30 |
- |
|
| 31 |
-PROG_NAME = 'derivepassphrase_export' |
|
| 16 |
+__all__ = () |
|
| 32 | 17 |
|
| 33 | 18 |
|
| 34 | 19 |
def get_vault_key() -> bytes: |
| ... | ... |
@@ -86,139 +71,3 @@ def get_vault_path() -> str | bytes | os.PathLike: |
| 86 | 71 |
msg = 'Cannot determine home directory' |
| 87 | 72 |
raise RuntimeError(msg) |
| 88 | 73 |
return result |
| 89 |
- |
|
| 90 |
- |
|
| 91 |
-@click.command( |
|
| 92 |
- context_settings={'help_option_names': ['-h', '--help']},
|
|
| 93 |
-) |
|
| 94 |
-@click.option( |
|
| 95 |
- '-f', |
|
| 96 |
- '--format', |
|
| 97 |
- 'formats', |
|
| 98 |
- metavar='FMT', |
|
| 99 |
- multiple=True, |
|
| 100 |
- default=('v0.3', 'v0.2', 'storeroom'),
|
|
| 101 |
- type=click.Choice(['v0.2', 'v0.3', 'storeroom']), |
|
| 102 |
- help='try the following storage formats, in order (default: v0.3, v0.2)', |
|
| 103 |
-) |
|
| 104 |
-@click.option( |
|
| 105 |
- '-k', |
|
| 106 |
- '--key', |
|
| 107 |
- metavar='K', |
|
| 108 |
- help=( |
|
| 109 |
- 'use K as the storage master key ' |
|
| 110 |
- '(default: check the `VAULT_KEY`, `LOGNAME`, `USER` or ' |
|
| 111 |
- '`USERNAME` environment variables)' |
|
| 112 |
- ), |
|
| 113 |
-) |
|
| 114 |
-@click.argument('path', metavar='PATH', required=True)
|
|
| 115 |
-@click.pass_context |
|
| 116 |
-def derivepassphrase_export( |
|
| 117 |
- ctx: click.Context, |
|
| 118 |
- /, |
|
| 119 |
- *, |
|
| 120 |
- path: str | bytes | os.PathLike[str], |
|
| 121 |
- formats: Sequence[Literal['v0.2', 'v0.3', 'storeroom']] = (), |
|
| 122 |
- key: str | bytes | None = None, |
|
| 123 |
-) -> None: |
|
| 124 |
- """Export a vault-native configuration to standard output. |
|
| 125 |
- |
|
| 126 |
- Read the vault-native configuration at PATH, extract all information |
|
| 127 |
- from it, and export the resulting configuration to standard output. |
|
| 128 |
- Depending on the configuration format, this may either be a file or |
|
| 129 |
- a directory. |
|
| 130 |
- |
|
| 131 |
- If PATH is explicitly given as `VAULT_PATH`, then use the |
|
| 132 |
- `VAULT_PATH` environment variable to determine the correct path. |
|
| 133 |
- (Use `./VAULT_PATH` or similar to indicate a file/directory actually |
|
| 134 |
- named `VAULT_PATH`.) |
|
| 135 |
- |
|
| 136 |
- """ |
|
| 137 |
- |
|
| 138 |
- def load_data( |
|
| 139 |
- fmt: Literal['v0.2', 'v0.3', 'storeroom'], |
|
| 140 |
- path: str | bytes | os.PathLike[str], |
|
| 141 |
- key: bytes, |
|
| 142 |
- ) -> Any: |
|
| 143 |
- contents: bytes |
|
| 144 |
- module: types.ModuleType |
|
| 145 |
- match fmt: |
|
| 146 |
- case 'v0.2': |
|
| 147 |
- module = importlib.import_module( |
|
| 148 |
- 'derivepassphrase.exporter.vault_v03_and_below' |
|
| 149 |
- ) |
|
| 150 |
- if module.STUBBED: |
|
| 151 |
- raise ModuleNotFoundError |
|
| 152 |
- with open(path, 'rb') as infile: |
|
| 153 |
- contents = base64.standard_b64decode(infile.read()) |
|
| 154 |
- return module.V02Reader(contents, key).run() |
|
| 155 |
- case 'v0.3': |
|
| 156 |
- module = importlib.import_module( |
|
| 157 |
- 'derivepassphrase.exporter.vault_v03_and_below' |
|
| 158 |
- ) |
|
| 159 |
- if module.STUBBED: |
|
| 160 |
- raise ModuleNotFoundError |
|
| 161 |
- with open(path, 'rb') as infile: |
|
| 162 |
- contents = base64.standard_b64decode(infile.read()) |
|
| 163 |
- return module.V03Reader(contents, key).run() |
|
| 164 |
- case 'storeroom': |
|
| 165 |
- module = importlib.import_module( |
|
| 166 |
- 'derivepassphrase.exporter.storeroom' |
|
| 167 |
- ) |
|
| 168 |
- if module.STUBBED: |
|
| 169 |
- raise ModuleNotFoundError |
|
| 170 |
- return module.export_storeroom_data(path, key) |
|
| 171 |
- case _: # pragma: no cover |
|
| 172 |
- assert_never(fmt) |
|
| 173 |
- |
|
| 174 |
- logging.basicConfig() |
|
| 175 |
- if path in {'VAULT_PATH', b'VAULT_PATH'}:
|
|
| 176 |
- path = get_vault_path() |
|
| 177 |
- if key is None: |
|
| 178 |
- key = get_vault_key() |
|
| 179 |
- elif isinstance(key, str): # pragma: no branch |
|
| 180 |
- key = key.encode('utf-8')
|
|
| 181 |
- for fmt in formats: |
|
| 182 |
- try: |
|
| 183 |
- config = load_data(fmt, path, key) |
|
| 184 |
- except ( |
|
| 185 |
- IsADirectoryError, |
|
| 186 |
- NotADirectoryError, |
|
| 187 |
- ValueError, |
|
| 188 |
- RuntimeError, |
|
| 189 |
- ): |
|
| 190 |
- logging.info('Cannot load as %s: %s', fmt, path)
|
|
| 191 |
- continue |
|
| 192 |
- except OSError as exc: |
|
| 193 |
- click.echo( |
|
| 194 |
- ( |
|
| 195 |
- f'{PROG_NAME}: ERROR: Cannot parse {path!r} as '
|
|
| 196 |
- f'a valid config: {exc.strerror}: {exc.filename!r}'
|
|
| 197 |
- ), |
|
| 198 |
- err=True, |
|
| 199 |
- ) |
|
| 200 |
- ctx.exit(1) |
|
| 201 |
- except ModuleNotFoundError: |
|
| 202 |
- # TODO(the-13th-letter): Use backslash continuation. |
|
| 203 |
- # https://github.com/nedbat/coveragepy/issues/1836 |
|
| 204 |
- msg = f""" |
|
| 205 |
-{PROG_NAME}: ERROR: Cannot load the required Python module "cryptography".
|
|
| 206 |
-{PROG_NAME}: INFO: pip users: see the "export" extra.
|
|
| 207 |
-""".lstrip('\n')
|
|
| 208 |
- click.echo(msg, nl=False, err=True) |
|
| 209 |
- ctx.exit(1) |
|
| 210 |
- else: |
|
| 211 |
- if not _types.is_vault_config(config): |
|
| 212 |
- click.echo( |
|
| 213 |
- f'{PROG_NAME}: ERROR: Invalid vault config: {config!r}',
|
|
| 214 |
- err=True, |
|
| 215 |
- ) |
|
| 216 |
- ctx.exit(1) |
|
| 217 |
- click.echo(json.dumps(config, indent=2, sort_keys=True)) |
|
| 218 |
- break |
|
| 219 |
- else: |
|
| 220 |
- click.echo( |
|
| 221 |
- f'{PROG_NAME}: ERROR: Cannot parse {path!r} as a valid config.',
|
|
| 222 |
- err=True, |
|
| 223 |
- ) |
|
| 224 |
- ctx.exit(1) |
| ... | ... |
@@ -0,0 +1,167 @@ |
| 1 |
+# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info> |
|
| 2 |
+# |
|
| 3 |
+# SPDX-License-Identifier: MIT |
|
| 4 |
+ |
|
| 5 |
+"""Command-line interface for derivepassphrase_export.""" |
|
| 6 |
+ |
|
| 7 |
+from __future__ import annotations |
|
| 8 |
+ |
|
| 9 |
+import base64 |
|
| 10 |
+import importlib |
|
| 11 |
+import json |
|
| 12 |
+import logging |
|
| 13 |
+from typing import TYPE_CHECKING, Any, Literal |
|
| 14 |
+ |
|
| 15 |
+import click |
|
| 16 |
+from typing_extensions import assert_never |
|
| 17 |
+ |
|
| 18 |
+import derivepassphrase as dpp |
|
| 19 |
+from derivepassphrase import _types, exporter |
|
| 20 |
+ |
|
| 21 |
+if TYPE_CHECKING: |
|
| 22 |
+ import os |
|
| 23 |
+ import types |
|
| 24 |
+ from collections.abc import Sequence |
|
| 25 |
+ |
|
| 26 |
+__author__ = dpp.__author__ |
|
| 27 |
+__version__ = dpp.__version__ |
|
| 28 |
+ |
|
| 29 |
+__all__ = ('derivepassphrase_export',)
|
|
| 30 |
+ |
|
| 31 |
+PROG_NAME = 'derivepassphrase_export' |
|
| 32 |
+ |
|
| 33 |
+ |
|
| 34 |
+@click.command( |
|
| 35 |
+ context_settings={'help_option_names': ['-h', '--help']},
|
|
| 36 |
+) |
|
| 37 |
+@click.option( |
|
| 38 |
+ '-f', |
|
| 39 |
+ '--format', |
|
| 40 |
+ 'formats', |
|
| 41 |
+ metavar='FMT', |
|
| 42 |
+ multiple=True, |
|
| 43 |
+ default=('v0.3', 'v0.2', 'storeroom'),
|
|
| 44 |
+ type=click.Choice(['v0.2', 'v0.3', 'storeroom']), |
|
| 45 |
+ help='try the following storage formats, in order (default: v0.3, v0.2)', |
|
| 46 |
+) |
|
| 47 |
+@click.option( |
|
| 48 |
+ '-k', |
|
| 49 |
+ '--key', |
|
| 50 |
+ metavar='K', |
|
| 51 |
+ help=( |
|
| 52 |
+ 'use K as the storage master key ' |
|
| 53 |
+ '(default: check the `VAULT_KEY`, `LOGNAME`, `USER` or ' |
|
| 54 |
+ '`USERNAME` environment variables)' |
|
| 55 |
+ ), |
|
| 56 |
+) |
|
| 57 |
+@click.argument('path', metavar='PATH', required=True)
|
|
| 58 |
+@click.pass_context |
|
| 59 |
+def derivepassphrase_export( |
|
| 60 |
+ ctx: click.Context, |
|
| 61 |
+ /, |
|
| 62 |
+ *, |
|
| 63 |
+ path: str | bytes | os.PathLike[str], |
|
| 64 |
+ formats: Sequence[Literal['v0.2', 'v0.3', 'storeroom']] = (), |
|
| 65 |
+ key: str | bytes | None = None, |
|
| 66 |
+) -> None: |
|
| 67 |
+ """Export a vault-native configuration to standard output. |
|
| 68 |
+ |
|
| 69 |
+ Read the vault-native configuration at PATH, extract all information |
|
| 70 |
+ from it, and export the resulting configuration to standard output. |
|
| 71 |
+ Depending on the configuration format, this may either be a file or |
|
| 72 |
+ a directory. |
|
| 73 |
+ |
|
| 74 |
+ If PATH is explicitly given as `VAULT_PATH`, then use the |
|
| 75 |
+ `VAULT_PATH` environment variable to determine the correct path. |
|
| 76 |
+ (Use `./VAULT_PATH` or similar to indicate a file/directory actually |
|
| 77 |
+ named `VAULT_PATH`.) |
|
| 78 |
+ |
|
| 79 |
+ """ |
|
| 80 |
+ |
|
| 81 |
+ def load_data( |
|
| 82 |
+ fmt: Literal['v0.2', 'v0.3', 'storeroom'], |
|
| 83 |
+ path: str | bytes | os.PathLike[str], |
|
| 84 |
+ key: bytes, |
|
| 85 |
+ ) -> Any: |
|
| 86 |
+ contents: bytes |
|
| 87 |
+ module: types.ModuleType |
|
| 88 |
+ match fmt: |
|
| 89 |
+ case 'v0.2': |
|
| 90 |
+ module = importlib.import_module( |
|
| 91 |
+ 'derivepassphrase.exporter.vault_v03_and_below' |
|
| 92 |
+ ) |
|
| 93 |
+ if module.STUBBED: |
|
| 94 |
+ raise ModuleNotFoundError |
|
| 95 |
+ with open(path, 'rb') as infile: |
|
| 96 |
+ contents = base64.standard_b64decode(infile.read()) |
|
| 97 |
+ return module.V02Reader(contents, key).run() |
|
| 98 |
+ case 'v0.3': |
|
| 99 |
+ module = importlib.import_module( |
|
| 100 |
+ 'derivepassphrase.exporter.vault_v03_and_below' |
|
| 101 |
+ ) |
|
| 102 |
+ if module.STUBBED: |
|
| 103 |
+ raise ModuleNotFoundError |
|
| 104 |
+ with open(path, 'rb') as infile: |
|
| 105 |
+ contents = base64.standard_b64decode(infile.read()) |
|
| 106 |
+ return module.V03Reader(contents, key).run() |
|
| 107 |
+ case 'storeroom': |
|
| 108 |
+ module = importlib.import_module( |
|
| 109 |
+ 'derivepassphrase.exporter.storeroom' |
|
| 110 |
+ ) |
|
| 111 |
+ if module.STUBBED: |
|
| 112 |
+ raise ModuleNotFoundError |
|
| 113 |
+ return module.export_storeroom_data(path, key) |
|
| 114 |
+ case _: # pragma: no cover |
|
| 115 |
+ assert_never(fmt) |
|
| 116 |
+ |
|
| 117 |
+ logging.basicConfig() |
|
| 118 |
+ if path in {'VAULT_PATH', b'VAULT_PATH'}:
|
|
| 119 |
+ path = exporter.get_vault_path() |
|
| 120 |
+ if key is None: |
|
| 121 |
+ key = exporter.get_vault_key() |
|
| 122 |
+ elif isinstance(key, str): # pragma: no branch |
|
| 123 |
+ key = key.encode('utf-8')
|
|
| 124 |
+ for fmt in formats: |
|
| 125 |
+ try: |
|
| 126 |
+ config = load_data(fmt, path, key) |
|
| 127 |
+ except ( |
|
| 128 |
+ IsADirectoryError, |
|
| 129 |
+ NotADirectoryError, |
|
| 130 |
+ ValueError, |
|
| 131 |
+ RuntimeError, |
|
| 132 |
+ ): |
|
| 133 |
+ logging.info('Cannot load as %s: %s', fmt, path)
|
|
| 134 |
+ continue |
|
| 135 |
+ except OSError as exc: |
|
| 136 |
+ click.echo( |
|
| 137 |
+ ( |
|
| 138 |
+ f'{PROG_NAME}: ERROR: Cannot parse {path!r} as '
|
|
| 139 |
+ f'a valid config: {exc.strerror}: {exc.filename!r}'
|
|
| 140 |
+ ), |
|
| 141 |
+ err=True, |
|
| 142 |
+ ) |
|
| 143 |
+ ctx.exit(1) |
|
| 144 |
+ except ModuleNotFoundError: |
|
| 145 |
+ # TODO(the-13th-letter): Use backslash continuation. |
|
| 146 |
+ # https://github.com/nedbat/coveragepy/issues/1836 |
|
| 147 |
+ msg = f""" |
|
| 148 |
+{PROG_NAME}: ERROR: Cannot load the required Python module "cryptography".
|
|
| 149 |
+{PROG_NAME}: INFO: pip users: see the "export" extra.
|
|
| 150 |
+""".lstrip('\n')
|
|
| 151 |
+ click.echo(msg, nl=False, err=True) |
|
| 152 |
+ ctx.exit(1) |
|
| 153 |
+ else: |
|
| 154 |
+ if not _types.is_vault_config(config): |
|
| 155 |
+ click.echo( |
|
| 156 |
+ f'{PROG_NAME}: ERROR: Invalid vault config: {config!r}',
|
|
| 157 |
+ err=True, |
|
| 158 |
+ ) |
|
| 159 |
+ ctx.exit(1) |
|
| 160 |
+ click.echo(json.dumps(config, indent=2, sort_keys=True)) |
|
| 161 |
+ break |
|
| 162 |
+ else: |
|
| 163 |
+ click.echo( |
|
| 164 |
+ f'{PROG_NAME}: ERROR: Cannot parse {path!r} as a valid config.',
|
|
| 165 |
+ err=True, |
|
| 166 |
+ ) |
|
| 167 |
+ ctx.exit(1) |
| ... | ... |
@@ -350,6 +350,99 @@ DUMMY_PHRASE_FROM_KEY1_RAW = ( |
| 350 | 350 |
) |
| 351 | 351 |
DUMMY_PHRASE_FROM_KEY1 = b'8JgZgGwal9UmA27M42WPhmYHExkTCSEzM/nkNlMdr/0NCB/s+HObjF9VORZ8U1QsHlK7MO1/ieIvaVFV2J6mAg==' # noqa: E501 |
| 352 | 352 |
|
| 353 |
+VAULT_MASTER_KEY = 'vault key' |
|
| 354 |
+VAULT_V02_CONFIG = 'P7xeh5y4jmjpJ2pFq4KUcTVoaE9ZOEkwWmpVTURSSWQxbGt6emN4aFE4eFM3anVPbDRNTGpOLzY3eDF5aE1YTm5LNWh5Q1BwWTMwM3M5S083MWRWRFlmOXNqSFJNcStGMWFOS3c2emhiOUNNenZYTmNNMnZxaUErdlRoOGF2ZHdGT1ZLNTNLOVJQcU9jWmJrR3g5N09VcVBRZ0ZnSFNUQy9HdFVWWnFteVhRVkY3MHNBdnF2ZWFEbFBseWRGelE1c3BFTnVUckRQdWJSL29wNjFxd2Y2ZVpob3VyVzRod3FKTElTenJ1WTZacTJFOFBtK3BnVzh0QWVxcWtyWFdXOXYyenNQeFNZbWt1MDU2Vm1kVGtISWIxWTBpcWRFbyswUVJudVVhZkVlNVpGWDA4WUQ2Q2JTWW81SnlhQ2Zxa3cxNmZoQjJES0Uyd29rNXpSck5iWVBrVmEwOXFya1NpMi9saU5LL3F0M3N3MjZKekNCem9ER2svWkZ0SUJLdmlHRno0VlQzQ3pqZTBWcTM3YmRiNmJjTkhqUHZoQ0NxMW1ldW1XOFVVK3pQMEtUMkRMVGNvNHFlOG40ck5KcGhsYXg1b1VzZ1NYU1B2T3RXdEkwYzg4NWE3YWUzOWI1MDI0MThhMWZjODQ3MDA2OTJmNDQ0MDkxNGFiNmRlMGQ2YjZiNjI5NGMwN2IwMmI4MGZi' # noqa: E501 |
|
| 355 |
+VAULT_V02_CONFIG_DATA = {
|
|
| 356 |
+ 'global': {
|
|
| 357 |
+ 'phrase': DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 358 |
+ }, |
|
| 359 |
+ 'services': {
|
|
| 360 |
+ '(meta)': {
|
|
| 361 |
+ 'notes': 'This config was originally in v0.2 format.', |
|
| 362 |
+ }, |
|
| 363 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
| 364 |
+ }, |
|
| 365 |
+} |
|
| 366 |
+VAULT_V03_CONFIG = 'sBPBrr8BFHPxSJkV/A53zk9zwDQHFxLe6UIusCVvzFQre103pcj5xxmE11lMTA0U2QTYjkhRXKkH5WegSmYpAnzReuRsYZlWWp6N4kkubf+twZ9C3EeggPm7as2Af4TICHVbX4uXpIHeQJf9y1OtqrO+SRBrgPBzgItoxsIxebxVKgyvh1CZQOSkn7BIzt9xKhDng3ubS4hQ91fB0QCumlldTbUl8tj4Xs5JbvsSlUMxRlVzZ0OgAOrSsoWELXmsp6zXFa9K6wIuZa4wQuMLQFHiA64JO1CR3I+rviWCeMlbTOuJNx6vMB5zotKJqA2hIUpN467TQ9vI4g/QTo40m5LT2EQKbIdTvBQAzcV4lOcpr5Lqt4LHED5mKvm/4YfpuuT3I3XCdWfdG5SB7ciiB4Go+xQdddy3zZMiwm1fEwIB8XjFf2cxoJdccLQ2yxf+9diedBP04EsMHrvxKDhQ7/vHl7xF2MMFTDKl3WFd23vvcjpR1JgNAKYprG/e1p/7' # noqa: E501 |
|
| 367 |
+VAULT_V03_CONFIG_DATA = {
|
|
| 368 |
+ 'global': {
|
|
| 369 |
+ 'phrase': DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 370 |
+ }, |
|
| 371 |
+ 'services': {
|
|
| 372 |
+ '(meta)': {
|
|
| 373 |
+ 'notes': 'This config was originally in v0.3 format.', |
|
| 374 |
+ }, |
|
| 375 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
| 376 |
+ }, |
|
| 377 |
+} |
|
| 378 |
+VAULT_STOREROOM_CONFIG_ZIPPED = b""" |
|
| 379 |
+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 |
|
| 429 |
+""" |
|
| 430 |
+VAULT_STOREROOM_CONFIG_DATA = {
|
|
| 431 |
+ 'global': {
|
|
| 432 |
+ 'phrase': DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 433 |
+ }, |
|
| 434 |
+ 'services': {
|
|
| 435 |
+ '(meta)': {
|
|
| 436 |
+ 'notes': 'This config was originally in storeroom format.', |
|
| 437 |
+ }, |
|
| 438 |
+ DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS.copy(), |
|
| 439 |
+ }, |
|
| 440 |
+} |
|
| 441 |
+ |
|
| 442 |
+CANNOT_LOAD_CRYPTOGRAPHY = ( |
|
| 443 |
+ b'Cannot load the required Python module "cryptography".' |
|
| 444 |
+) |
|
| 445 |
+ |
|
| 353 | 446 |
skip_if_no_agent = pytest.mark.skipif( |
| 354 | 447 |
not os.environ.get('SSH_AUTH_SOCK'), reason='running SSH agent required'
|
| 355 | 448 |
) |
| ... | ... |
@@ -427,7 +520,7 @@ def isolated_vault_exporter_config( |
| 427 | 520 |
except AttributeError: |
| 428 | 521 |
|
| 429 | 522 |
@contextlib.contextmanager |
| 430 |
- def chdir(newpath: str) -> Iterator[None]: |
|
| 523 |
+ def chdir(newpath: str) -> Iterator[None]: # pragma: no branch |
|
| 431 | 524 |
oldpath = os.getcwd() |
| 432 | 525 |
os.chdir(newpath) |
| 433 | 526 |
yield |
| ... | ... |
@@ -13,107 +13,7 @@ import pytest |
| 13 | 13 |
|
| 14 | 14 |
import tests |
| 15 | 15 |
from derivepassphrase import exporter |
| 16 |
- |
|
| 17 |
-VAULT_MASTER_KEY = 'vault key' |
|
| 18 |
-VAULT_V02_CONFIG = 'P7xeh5y4jmjpJ2pFq4KUcTVoaE9ZOEkwWmpVTURSSWQxbGt6emN4aFE4eFM3anVPbDRNTGpOLzY3eDF5aE1YTm5LNWh5Q1BwWTMwM3M5S083MWRWRFlmOXNqSFJNcStGMWFOS3c2emhiOUNNenZYTmNNMnZxaUErdlRoOGF2ZHdGT1ZLNTNLOVJQcU9jWmJrR3g5N09VcVBRZ0ZnSFNUQy9HdFVWWnFteVhRVkY3MHNBdnF2ZWFEbFBseWRGelE1c3BFTnVUckRQdWJSL29wNjFxd2Y2ZVpob3VyVzRod3FKTElTenJ1WTZacTJFOFBtK3BnVzh0QWVxcWtyWFdXOXYyenNQeFNZbWt1MDU2Vm1kVGtISWIxWTBpcWRFbyswUVJudVVhZkVlNVpGWDA4WUQ2Q2JTWW81SnlhQ2Zxa3cxNmZoQjJES0Uyd29rNXpSck5iWVBrVmEwOXFya1NpMi9saU5LL3F0M3N3MjZKekNCem9ER2svWkZ0SUJLdmlHRno0VlQzQ3pqZTBWcTM3YmRiNmJjTkhqUHZoQ0NxMW1ldW1XOFVVK3pQMEtUMkRMVGNvNHFlOG40ck5KcGhsYXg1b1VzZ1NYU1B2T3RXdEkwYzg4NWE3YWUzOWI1MDI0MThhMWZjODQ3MDA2OTJmNDQ0MDkxNGFiNmRlMGQ2YjZiNjI5NGMwN2IwMmI4MGZi' # noqa: E501 |
|
| 19 |
-VAULT_V02_CONFIG_DATA = {
|
|
| 20 |
- 'global': {
|
|
| 21 |
- 'phrase': tests.DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 22 |
- }, |
|
| 23 |
- 'services': {
|
|
| 24 |
- '(meta)': {
|
|
| 25 |
- 'notes': 'This config was originally in v0.2 format.', |
|
| 26 |
- }, |
|
| 27 |
- tests.DUMMY_SERVICE: tests.DUMMY_CONFIG_SETTINGS.copy(), |
|
| 28 |
- }, |
|
| 29 |
-} |
|
| 30 |
-VAULT_V03_CONFIG = 'sBPBrr8BFHPxSJkV/A53zk9zwDQHFxLe6UIusCVvzFQre103pcj5xxmE11lMTA0U2QTYjkhRXKkH5WegSmYpAnzReuRsYZlWWp6N4kkubf+twZ9C3EeggPm7as2Af4TICHVbX4uXpIHeQJf9y1OtqrO+SRBrgPBzgItoxsIxebxVKgyvh1CZQOSkn7BIzt9xKhDng3ubS4hQ91fB0QCumlldTbUl8tj4Xs5JbvsSlUMxRlVzZ0OgAOrSsoWELXmsp6zXFa9K6wIuZa4wQuMLQFHiA64JO1CR3I+rviWCeMlbTOuJNx6vMB5zotKJqA2hIUpN467TQ9vI4g/QTo40m5LT2EQKbIdTvBQAzcV4lOcpr5Lqt4LHED5mKvm/4YfpuuT3I3XCdWfdG5SB7ciiB4Go+xQdddy3zZMiwm1fEwIB8XjFf2cxoJdccLQ2yxf+9diedBP04EsMHrvxKDhQ7/vHl7xF2MMFTDKl3WFd23vvcjpR1JgNAKYprG/e1p/7' # noqa: E501 |
|
| 31 |
-VAULT_V03_CONFIG_DATA = {
|
|
| 32 |
- 'global': {
|
|
| 33 |
- 'phrase': tests.DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 34 |
- }, |
|
| 35 |
- 'services': {
|
|
| 36 |
- '(meta)': {
|
|
| 37 |
- 'notes': 'This config was originally in v0.3 format.', |
|
| 38 |
- }, |
|
| 39 |
- tests.DUMMY_SERVICE: tests.DUMMY_CONFIG_SETTINGS.copy(), |
|
| 40 |
- }, |
|
| 41 |
-} |
|
| 42 |
-VAULT_STOREROOM_CONFIG_ZIPPED = b""" |
|
| 43 |
-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
-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
-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
-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
-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
-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
-wGA5N8ksjpFNCu5BZGu953GdoVnOfN6PNXoluWOS2JzO23ELNJ2m9nDn0uDhwC39VHJT1pQdejIw |
|
| 50 |
-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
-40QorG1uOBU7nPlDx/cCTDpSqwTZDkkAt6Zy9RT61NUZqHSMIgKMerj3njXOK+1q5sA/upSGvMrN |
|
| 52 |
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-D1EHqTWQvFZB5ANGpLMM0U10xWYAClzuVJXKm/n/8JgVaobY38IjzxXyk4iPkQUuYtws73Kan871 |
|
| 66 |
-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
-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
-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
-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
-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
-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
-IXUlM/2sGKKuY5sYrW5N+oGqit2zLBYv57mFvH/S8pWGYDGzUnU1CdTL3B4Yix+Hk8E/+m0cSi2E |
|
| 73 |
-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
-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
-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
-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
-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
-AgOcrM+JFvpYA86Ja8HCqCg8LihEI1e7+m8F71Lpavv/UEsDBBQAAAAIAJ1WGVnKO2Ji+AEAAGsC |
|
| 79 |
-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
-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
-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
-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
-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
-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
-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
-+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
-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
-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
-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
-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
-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
-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 |
|
| 93 |
-""" |
|
| 94 |
-VAULT_STOREROOM_CONFIG_DATA = {
|
|
| 95 |
- 'global': {
|
|
| 96 |
- 'phrase': tests.DUMMY_PASSPHRASE.decode('utf-8').rstrip('\n'),
|
|
| 97 |
- }, |
|
| 98 |
- 'services': {
|
|
| 99 |
- '(meta)': {
|
|
| 100 |
- 'notes': 'This config was originally in storeroom format.', |
|
| 101 |
- }, |
|
| 102 |
- tests.DUMMY_SERVICE: tests.DUMMY_CONFIG_SETTINGS.copy(), |
|
| 103 |
- }, |
|
| 104 |
-} |
|
| 105 |
- |
|
| 106 |
-try: |
|
| 107 |
- from cryptography.hazmat.primitives import ciphers |
|
| 108 |
-except ModuleNotFoundError: |
|
| 109 |
- CRYPTOGRAPHY_SUPPORT = False |
|
| 110 |
-else: |
|
| 111 |
- CRYPTOGRAPHY_SUPPORT = True |
|
| 112 |
- del ciphers |
|
| 113 |
- |
|
| 114 |
-CANNOT_LOAD_CRYPTOGRAPHY = ( |
|
| 115 |
- b'Cannot load the required Python module "cryptography".' |
|
| 116 |
-) |
|
| 16 |
+from derivepassphrase.exporter import cli |
|
| 117 | 17 |
|
| 118 | 18 |
|
| 119 | 19 |
class Test001ExporterUtils: |
| ... | ... |
@@ -206,91 +106,6 @@ class Test001ExporterUtils: |
| 206 | 106 |
|
| 207 | 107 |
|
| 208 | 108 |
class Test002CLI: |
| 209 |
- @pytest.mark.xfail( |
|
| 210 |
- not CRYPTOGRAPHY_SUPPORT, reason='cryptography module not found' |
|
| 211 |
- ) |
|
| 212 |
- def test_200_path_parameter(self, monkeypatch: pytest.MonkeyPatch) -> None: |
|
| 213 |
- runner = click.testing.CliRunner(mix_stderr=False) |
|
| 214 |
- with tests.isolated_vault_exporter_config( |
|
| 215 |
- monkeypatch=monkeypatch, |
|
| 216 |
- runner=runner, |
|
| 217 |
- vault_config=VAULT_V03_CONFIG, |
|
| 218 |
- vault_key=VAULT_MASTER_KEY, |
|
| 219 |
- ): |
|
| 220 |
- monkeypatch.setenv('VAULT_KEY', VAULT_MASTER_KEY)
|
|
| 221 |
- result = runner.invoke( |
|
| 222 |
- exporter.derivepassphrase_export, |
|
| 223 |
- ['VAULT_PATH'], |
|
| 224 |
- ) |
|
| 225 |
- assert not result.exception |
|
| 226 |
- assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 227 |
- assert json.loads(result.stdout) == VAULT_V03_CONFIG_DATA |
|
| 228 |
- |
|
| 229 |
- @pytest.mark.xfail( |
|
| 230 |
- not CRYPTOGRAPHY_SUPPORT, reason='cryptography module not found' |
|
| 231 |
- ) |
|
| 232 |
- def test_201_key_parameter(self, monkeypatch: pytest.MonkeyPatch) -> None: |
|
| 233 |
- runner = click.testing.CliRunner(mix_stderr=False) |
|
| 234 |
- with tests.isolated_vault_exporter_config( |
|
| 235 |
- monkeypatch=monkeypatch, |
|
| 236 |
- runner=runner, |
|
| 237 |
- vault_config=VAULT_V03_CONFIG, |
|
| 238 |
- ): |
|
| 239 |
- result = runner.invoke( |
|
| 240 |
- exporter.derivepassphrase_export, |
|
| 241 |
- ['-k', VAULT_MASTER_KEY, '.vault'], |
|
| 242 |
- ) |
|
| 243 |
- assert not result.exception |
|
| 244 |
- assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 245 |
- assert json.loads(result.stdout) == VAULT_V03_CONFIG_DATA |
|
| 246 |
- |
|
| 247 |
- @pytest.mark.xfail( |
|
| 248 |
- not CRYPTOGRAPHY_SUPPORT, reason='cryptography module not found' |
|
| 249 |
- ) |
|
| 250 |
- @pytest.mark.parametrize( |
|
| 251 |
- ['version', 'config', 'config_data'], |
|
| 252 |
- [ |
|
| 253 |
- pytest.param( |
|
| 254 |
- '0.2', VAULT_V02_CONFIG, VAULT_V02_CONFIG_DATA, id='0.2' |
|
| 255 |
- ), |
|
| 256 |
- pytest.param( |
|
| 257 |
- '0.3', VAULT_V03_CONFIG, VAULT_V03_CONFIG_DATA, id='0.3' |
|
| 258 |
- ), |
|
| 259 |
- pytest.param( |
|
| 260 |
- 'storeroom', |
|
| 261 |
- VAULT_STOREROOM_CONFIG_ZIPPED, |
|
| 262 |
- VAULT_STOREROOM_CONFIG_DATA, |
|
| 263 |
- id='storeroom', |
|
| 264 |
- ), |
|
| 265 |
- ], |
|
| 266 |
- ) |
|
| 267 |
- def test_210_load_vault_v02_v03_storeroom( |
|
| 268 |
- self, |
|
| 269 |
- monkeypatch: pytest.MonkeyPatch, |
|
| 270 |
- version: str, |
|
| 271 |
- config: str | bytes, |
|
| 272 |
- config_data: dict[str, Any], |
|
| 273 |
- ) -> None: |
|
| 274 |
- runner = click.testing.CliRunner(mix_stderr=False) |
|
| 275 |
- with tests.isolated_vault_exporter_config( |
|
| 276 |
- monkeypatch=monkeypatch, |
|
| 277 |
- runner=runner, |
|
| 278 |
- vault_config=config, |
|
| 279 |
- ): |
|
| 280 |
- result = runner.invoke( |
|
| 281 |
- exporter.derivepassphrase_export, |
|
| 282 |
- [ |
|
| 283 |
- '-f', |
|
| 284 |
- f'v{version}' if version.startswith('0') else version,
|
|
| 285 |
- '-k', |
|
| 286 |
- VAULT_MASTER_KEY, |
|
| 287 |
- 'VAULT_PATH', |
|
| 288 |
- ], |
|
| 289 |
- ) |
|
| 290 |
- assert not result.exception |
|
| 291 |
- assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 292 |
- assert json.loads(result.stdout) == config_data |
|
| 293 |
- |
|
| 294 | 109 |
def test_300_invalid_format( |
| 295 | 110 |
self, |
| 296 | 111 |
monkeypatch: pytest.MonkeyPatch, |
| ... | ... |
@@ -299,11 +114,11 @@ class Test002CLI: |
| 299 | 114 |
with tests.isolated_vault_exporter_config( |
| 300 | 115 |
monkeypatch=monkeypatch, |
| 301 | 116 |
runner=runner, |
| 302 |
- vault_config=VAULT_V03_CONFIG, |
|
| 303 |
- vault_key=VAULT_MASTER_KEY, |
|
| 117 |
+ vault_config=tests.VAULT_V03_CONFIG, |
|
| 118 |
+ vault_key=tests.VAULT_MASTER_KEY, |
|
| 304 | 119 |
): |
| 305 | 120 |
result = runner.invoke( |
| 306 |
- exporter.derivepassphrase_export, |
|
| 121 |
+ cli.derivepassphrase_export, |
|
| 307 | 122 |
['-f', 'INVALID', 'VAULT_PATH'], |
| 308 | 123 |
catch_exceptions=False, |
| 309 | 124 |
) |
| ... | ... |
@@ -314,68 +129,3 @@ class Test002CLI: |
| 314 | 129 |
assert b'-f' in result.stderr_bytes |
| 315 | 130 |
assert b'--format' in result.stderr_bytes |
| 316 | 131 |
assert b'INVALID' in result.stderr_bytes |
| 317 |
- |
|
| 318 |
- @pytest.mark.xfail( |
|
| 319 |
- not CRYPTOGRAPHY_SUPPORT, reason='cryptography module not found' |
|
| 320 |
- ) |
|
| 321 |
- def test_301_vault_config_not_found( |
|
| 322 |
- self, |
|
| 323 |
- monkeypatch: pytest.MonkeyPatch, |
|
| 324 |
- ) -> None: |
|
| 325 |
- runner = click.testing.CliRunner(mix_stderr=False) |
|
| 326 |
- with tests.isolated_vault_exporter_config( |
|
| 327 |
- monkeypatch=monkeypatch, |
|
| 328 |
- runner=runner, |
|
| 329 |
- vault_config=VAULT_V03_CONFIG, |
|
| 330 |
- vault_key=VAULT_MASTER_KEY, |
|
| 331 |
- ): |
|
| 332 |
- result = runner.invoke( |
|
| 333 |
- exporter.derivepassphrase_export, |
|
| 334 |
- ['does-not-exist.txt'], |
|
| 335 |
- ) |
|
| 336 |
- assert isinstance(result.exception, SystemExit) |
|
| 337 |
- assert result.exit_code |
|
| 338 |
- assert result.stderr_bytes |
|
| 339 |
- assert ( |
|
| 340 |
- b"Cannot parse 'does-not-exist.txt' as a valid config" |
|
| 341 |
- in result.stderr_bytes |
|
| 342 |
- ) |
|
| 343 |
- assert CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr_bytes |
|
| 344 |
- |
|
| 345 |
- @pytest.mark.xfail( |
|
| 346 |
- not CRYPTOGRAPHY_SUPPORT, reason='cryptography module not found' |
|
| 347 |
- ) |
|
| 348 |
- def test_302_vault_config_invalid( |
|
| 349 |
- self, |
|
| 350 |
- monkeypatch: pytest.MonkeyPatch, |
|
| 351 |
- ) -> None: |
|
| 352 |
- runner = click.testing.CliRunner(mix_stderr=False) |
|
| 353 |
- with tests.isolated_vault_exporter_config( |
|
| 354 |
- monkeypatch=monkeypatch, |
|
| 355 |
- runner=runner, |
|
| 356 |
- vault_config='', |
|
| 357 |
- vault_key=VAULT_MASTER_KEY, |
|
| 358 |
- ): |
|
| 359 |
- result = runner.invoke( |
|
| 360 |
- exporter.derivepassphrase_export, |
|
| 361 |
- ['.vault'], |
|
| 362 |
- ) |
|
| 363 |
- assert isinstance(result.exception, SystemExit) |
|
| 364 |
- assert result.exit_code |
|
| 365 |
- assert result.stderr_bytes |
|
| 366 |
- assert ( |
|
| 367 |
- b"Cannot parse '.vault' as a valid config." in result.stderr_bytes |
|
| 368 |
- ) |
|
| 369 |
- assert CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr_bytes |
|
| 370 |
- |
|
| 371 |
- |
|
| 372 |
-class TestStoreroomExporter: |
|
| 373 |
- pass # TODO(the-13th-letter): Fill in once design is stable. |
|
| 374 |
- |
|
| 375 |
- |
|
| 376 |
-class TestV02Exporter: |
|
| 377 |
- pass # TODO(the-13th-letter): Fill in once design is stable. |
|
| 378 |
- |
|
| 379 |
- |
|
| 380 |
-class TestV03Exporter: |
|
| 381 |
- pass # TODO(the-13th-letter): Fill in once design is stable. |
| ... | ... |
@@ -0,0 +1,146 @@ |
| 1 |
+# SPDX-FileCopyrightText: 2024 Marco Ricci <m@the13thletter.info> |
|
| 2 |
+# |
|
| 3 |
+# SPDX-License-Identifier: MIT |
|
| 4 |
+ |
|
| 5 |
+from __future__ import annotations |
|
| 6 |
+ |
|
| 7 |
+import json |
|
| 8 |
+import os |
|
| 9 |
+from typing import Any |
|
| 10 |
+ |
|
| 11 |
+import click.testing |
|
| 12 |
+import pytest |
|
| 13 |
+ |
|
| 14 |
+import tests |
|
| 15 |
+from derivepassphrase import exporter |
|
| 16 |
+from derivepassphrase.exporter import cli |
|
| 17 |
+ |
|
| 18 |
+cryptography = pytest.importorskip('cryptography', minversion='38.0')
|
|
| 19 |
+ |
|
| 20 |
+ |
|
| 21 |
+class TestCLI: |
|
| 22 |
+ def test_200_path_parameter(self, monkeypatch: pytest.MonkeyPatch) -> None: |
|
| 23 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 24 |
+ with tests.isolated_vault_exporter_config( |
|
| 25 |
+ monkeypatch=monkeypatch, |
|
| 26 |
+ runner=runner, |
|
| 27 |
+ vault_config=tests.VAULT_V03_CONFIG, |
|
| 28 |
+ vault_key=tests.VAULT_MASTER_KEY, |
|
| 29 |
+ ): |
|
| 30 |
+ monkeypatch.setenv('VAULT_KEY', tests.VAULT_MASTER_KEY)
|
|
| 31 |
+ result = runner.invoke( |
|
| 32 |
+ cli.derivepassphrase_export, |
|
| 33 |
+ ['VAULT_PATH'], |
|
| 34 |
+ ) |
|
| 35 |
+ assert not result.exception |
|
| 36 |
+ assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 37 |
+ assert json.loads(result.stdout) == tests.VAULT_V03_CONFIG_DATA |
|
| 38 |
+ |
|
| 39 |
+ def test_201_key_parameter(self, monkeypatch: pytest.MonkeyPatch) -> None: |
|
| 40 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 41 |
+ with tests.isolated_vault_exporter_config( |
|
| 42 |
+ monkeypatch=monkeypatch, |
|
| 43 |
+ runner=runner, |
|
| 44 |
+ vault_config=tests.VAULT_V03_CONFIG, |
|
| 45 |
+ ): |
|
| 46 |
+ result = runner.invoke( |
|
| 47 |
+ cli.derivepassphrase_export, |
|
| 48 |
+ ['-k', tests.VAULT_MASTER_KEY, '.vault'], |
|
| 49 |
+ ) |
|
| 50 |
+ assert not result.exception |
|
| 51 |
+ assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 52 |
+ assert json.loads(result.stdout) == tests.VAULT_V03_CONFIG_DATA |
|
| 53 |
+ |
|
| 54 |
+ @pytest.mark.parametrize( |
|
| 55 |
+ ['format', 'config', 'config_data'], |
|
| 56 |
+ [ |
|
| 57 |
+ pytest.param( |
|
| 58 |
+ 'v0.2', |
|
| 59 |
+ tests.VAULT_V02_CONFIG, |
|
| 60 |
+ tests.VAULT_V02_CONFIG_DATA, |
|
| 61 |
+ id='0.2', |
|
| 62 |
+ ), |
|
| 63 |
+ pytest.param( |
|
| 64 |
+ 'v0.3', |
|
| 65 |
+ tests.VAULT_V03_CONFIG, |
|
| 66 |
+ tests.VAULT_V03_CONFIG_DATA, |
|
| 67 |
+ id='0.3', |
|
| 68 |
+ ), |
|
| 69 |
+ pytest.param( |
|
| 70 |
+ 'storeroom', |
|
| 71 |
+ tests.VAULT_STOREROOM_CONFIG_ZIPPED, |
|
| 72 |
+ tests.VAULT_STOREROOM_CONFIG_DATA, |
|
| 73 |
+ id='storeroom', |
|
| 74 |
+ ), |
|
| 75 |
+ ], |
|
| 76 |
+ ) |
|
| 77 |
+ def test_210_load_vault_v02_v03_storeroom( |
|
| 78 |
+ self, |
|
| 79 |
+ monkeypatch: pytest.MonkeyPatch, |
|
| 80 |
+ format: str, |
|
| 81 |
+ config: str | bytes, |
|
| 82 |
+ config_data: dict[str, Any], |
|
| 83 |
+ ) -> None: |
|
| 84 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 85 |
+ with tests.isolated_vault_exporter_config( |
|
| 86 |
+ monkeypatch=monkeypatch, |
|
| 87 |
+ runner=runner, |
|
| 88 |
+ vault_config=config, |
|
| 89 |
+ ): |
|
| 90 |
+ result = runner.invoke( |
|
| 91 |
+ cli.derivepassphrase_export, |
|
| 92 |
+ ['-f', format, '-k', tests.VAULT_MASTER_KEY, 'VAULT_PATH'], |
|
| 93 |
+ ) |
|
| 94 |
+ assert not result.exception |
|
| 95 |
+ assert (result.exit_code, result.stderr_bytes) == (0, b'') |
|
| 96 |
+ assert json.loads(result.stdout) == config_data |
|
| 97 |
+ |
|
| 98 |
+ # test_300_invalid_format is found in |
|
| 99 |
+ # tests.test_derivepassphrase_export::Test002CLI |
|
| 100 |
+ |
|
| 101 |
+ def test_301_vault_config_not_found( |
|
| 102 |
+ self, |
|
| 103 |
+ monkeypatch: pytest.MonkeyPatch, |
|
| 104 |
+ ) -> None: |
|
| 105 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 106 |
+ with tests.isolated_vault_exporter_config( |
|
| 107 |
+ monkeypatch=monkeypatch, |
|
| 108 |
+ runner=runner, |
|
| 109 |
+ vault_config=tests.VAULT_V03_CONFIG, |
|
| 110 |
+ vault_key=tests.VAULT_MASTER_KEY, |
|
| 111 |
+ ): |
|
| 112 |
+ result = runner.invoke( |
|
| 113 |
+ cli.derivepassphrase_export, |
|
| 114 |
+ ['does-not-exist.txt'], |
|
| 115 |
+ ) |
|
| 116 |
+ assert isinstance(result.exception, SystemExit) |
|
| 117 |
+ assert result.exit_code |
|
| 118 |
+ assert result.stderr_bytes |
|
| 119 |
+ assert ( |
|
| 120 |
+ b"Cannot parse 'does-not-exist.txt' as a valid config" |
|
| 121 |
+ in result.stderr_bytes |
|
| 122 |
+ ) |
|
| 123 |
+ assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr_bytes |
|
| 124 |
+ |
|
| 125 |
+ def test_302_vault_config_invalid( |
|
| 126 |
+ self, |
|
| 127 |
+ monkeypatch: pytest.MonkeyPatch, |
|
| 128 |
+ ) -> None: |
|
| 129 |
+ runner = click.testing.CliRunner(mix_stderr=False) |
|
| 130 |
+ with tests.isolated_vault_exporter_config( |
|
| 131 |
+ monkeypatch=monkeypatch, |
|
| 132 |
+ runner=runner, |
|
| 133 |
+ vault_config='', |
|
| 134 |
+ vault_key=tests.VAULT_MASTER_KEY, |
|
| 135 |
+ ): |
|
| 136 |
+ result = runner.invoke( |
|
| 137 |
+ cli.derivepassphrase_export, |
|
| 138 |
+ ['.vault'], |
|
| 139 |
+ ) |
|
| 140 |
+ assert isinstance(result.exception, SystemExit) |
|
| 141 |
+ assert result.exit_code |
|
| 142 |
+ assert result.stderr_bytes |
|
| 143 |
+ assert ( |
|
| 144 |
+ b"Cannot parse '.vault' as a valid config." in result.stderr_bytes |
|
| 145 |
+ ) |
|
| 146 |
+ assert tests.CANNOT_LOAD_CRYPTOGRAPHY not in result.stderr_bytes |
|
| 0 | 147 |