5e5c12b78d541fd09e37595f1ba63deb22c82847
Marco Ricci Change the author e-mail ad...

Marco Ricci authored 4 months ago

1) # SPDX-FileCopyrightText: 2024 Marco Ricci <software@the13thletter.info>
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

2) #
3) # SPDX-License-Identifier: MIT
4) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

5) # ruff: noqa: TRY400
6) 
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

7) """Command-line interface for derivepassphrase."""
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

8) 
9) from __future__ import annotations
10) 
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

11) import base64
12) import collections
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

13) import copy
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

14) import enum
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

15) import importlib
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

16) import inspect
17) import json
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

18) import logging
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

19) import os
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

20) import sys
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

21) import unicodedata
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

22) import warnings
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

23) from typing import (
24)     TYPE_CHECKING,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

25)     Callable,
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

26)     Literal,
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

27)     NoReturn,
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

28)     TextIO,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

29)     TypeVar,
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

30)     cast,
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

31) )
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

32) 
33) import click
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

34) from typing_extensions import (
35)     Any,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

36)     ParamSpec,
37)     Self,
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

38)     assert_never,
39) )
40) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

41) import derivepassphrase as dpp
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

42) from derivepassphrase import _types, exporter, ssh_agent, vault
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

43) 
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

44) if sys.version_info >= (3, 11):
45)     import tomllib
46) else:
47)     import tomli as tomllib
48) 
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

49) if TYPE_CHECKING:
50)     import pathlib
Marco Ricci Support one-off SSH agent c...

Marco Ricci authored 1 month ago

51)     import socket
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

52)     import types
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

53)     from collections.abc import (
54)         Iterator,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

55)         MutableSequence,
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

56)         Sequence,
57)     )
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

58) 
59) __author__ = dpp.__author__
60) __version__ = dpp.__version__
61) 
62) __all__ = ('derivepassphrase',)
63) 
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

64) PROG_NAME = 'derivepassphrase'
Marco Ricci Make suitable SSH key listi...

Marco Ricci authored 1 month ago

65) KEY_DISPLAY_LENGTH = 50
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

66) 
67) # Error messages
68) _INVALID_VAULT_CONFIG = 'Invalid vault config'
69) _AGENT_COMMUNICATION_ERROR = 'Error communicating with the SSH agent'
70) _NO_USABLE_KEYS = 'No usable SSH keys were found'
71) _EMPTY_SELECTION = 'Empty selection'
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

72) 
73) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

74) # Logging
75) # =======
76) 
77) 
78) class ClickEchoStderrHandler(logging.Handler):
79)     """A [`logging.Handler`][] for `click` applications.
80) 
81)     Outputs log messages to [`sys.stderr`][] via [`click.echo`][].
82) 
83)     """
84) 
85)     def emit(self, record: logging.LogRecord) -> None:
86)         """Emit a log record.
87) 
88)         Format the log record, then emit it via [`click.echo`][] to
89)         [`sys.stderr`][].
90) 
91)         """
92)         click.echo(self.format(record), err=True)
93) 
94) 
95) class CLIofPackageFormatter(logging.Formatter):
96)     """A [`logging.LogRecord`][] formatter for the CLI of a Python package.
97) 
98)     Assuming a package `PKG` and loggers within the same hierarchy
99)     `PKG`, format all log records from that hierarchy for proper user
100)     feedback on the console.  Intended for use with [`click`][CLICK] and
101)     when `PKG` provides a command-line tool `PKG` and when logs from
102)     that package should show up as output of the command-line tool.
103) 
104)     Essentially, this prepends certain short strings to the log message
105)     lines to make them readable as standard error output.
106) 
107)     Because this log output is intended to be displayed on standard
108)     error as high-level diagnostic output, you are strongly discouraged
109)     from changing the output format to include more tokens besides the
110)     log message.  Use a dedicated log file handler instead, without this
111)     formatter.
112) 
113)     [CLICK]: https://pypi.org/projects/click/
114) 
115)     """
116) 
117)     def __init__(
118)         self,
119)         *,
120)         prog_name: str = PROG_NAME,
121)         package_name: str | None = None,
122)     ) -> None:
123)         self.prog_name = prog_name
124)         self.package_name = (
125)             package_name
126)             if package_name is not None
127)             else prog_name.lower().replace(' ', '_').replace('-', '_')
128)         )
129) 
130)     def format(self, record: logging.LogRecord) -> str:
131)         """Format a log record suitably for standard error console output.
132) 
133)         Prepend the formatted string `"PROG_NAME: LABEL"` to each line
134)         of the message, where `PROG_NAME` is the program name, and
135)         `LABEL` depends on the record's level and on the logger name as
136)         follows:
137) 
138)           * For records at level [`logging.DEBUG`][], `LABEL` is
139)             `"Debug: "`.
140)           * For records at level [`logging.INFO`][], `LABEL` is the
141)             empty string.
142)           * For records at level [`logging.WARNING`][], `LABEL` is
143)             `"Deprecation warning: "` if the logger is named
144)             `PKG.deprecation` (where `PKG` is the package name), else
145)             `"Warning: "`.
146)           * For records at level [`logging.ERROR`][] and
147)             [`logging.CRITICAL`][] `"Error: "`, `LABEL` is `"ERROR: "`.
148) 
149)         The level indication strings at level `WARNING` or above are
150)         highlighted.  Use [`click.echo`][] to output them and remove
151)         color output if necessary.
152) 
153)         Args:
154)             record: A log record.
155) 
156)         Returns:
157)             A formatted log record.
158) 
159)         Raises:
160)             AssertionError:
161)                 The log level is not supported.
162) 
163)         """
164)         preliminary_result = record.getMessage()
165)         prefix = f'{self.prog_name}: '
166)         if record.levelname == 'DEBUG':  # pragma: no cover
167)             level_indicator = 'Debug: '
168)         elif record.levelname == 'INFO':
169)             level_indicator = ''
170)         elif record.levelname == 'WARNING':
171)             level_indicator = (
172)                 f'{click.style("Deprecation warning", bold=True)}: '
173)                 if record.name.endswith('.deprecation')
174)                 else f'{click.style("Warning", bold=True)}: '
175)             )
176)         elif record.levelname in {'ERROR', 'CRITICAL'}:
177)             level_indicator = ''
178)         else:  # pragma: no cover
179)             msg = f'Unsupported logging level: {record.levelname}'
180)             raise AssertionError(msg)
181)         return ''.join(
182)             prefix + level_indicator + line
183)             for line in preliminary_result.splitlines(True)  # noqa: FBT003
184)         )
185) 
186) 
187) class StandardCLILogging:
188)     """Set up CLI logging handlers upon instantiation."""
189) 
190)     prog_name = PROG_NAME
191)     package_name = PROG_NAME.lower().replace(' ', '_').replace('-', '_')
192)     cli_formatter = CLIofPackageFormatter(
193)         prog_name=prog_name, package_name=package_name
194)     )
195)     cli_handler = ClickEchoStderrHandler()
196)     cli_handler.addFilter(logging.Filter(name=package_name))
197)     cli_handler.setFormatter(cli_formatter)
198)     cli_handler.setLevel(logging.WARNING)
199)     warnings_handler = ClickEchoStderrHandler()
200)     warnings_handler.addFilter(logging.Filter(name='py.warnings'))
201)     warnings_handler.setFormatter(cli_formatter)
202)     warnings_handler.setLevel(logging.WARNING)
203) 
204)     @classmethod
205)     def ensure_standard_logging(cls) -> StandardLoggingContextManager:
206)         """Return a context manager to ensure standard logging is set up."""
207)         return StandardLoggingContextManager(
208)             handler=cls.cli_handler,
209)             root_logger=cls.package_name,
210)         )
211) 
212)     @classmethod
213)     def ensure_standard_warnings_logging(
214)         cls,
215)     ) -> StandardWarningsLoggingContextManager:
216)         """Return a context manager to ensure warnings logging is set up."""
217)         return StandardWarningsLoggingContextManager(
218)             handler=cls.warnings_handler,
219)         )
220) 
221) 
222) class StandardLoggingContextManager:
223)     """A reentrant context manager setting up standard CLI logging.
224) 
225)     Ensures that the given handler (defaulting to the CLI logging
226)     handler) is added to the named logger (defaulting to the root
227)     logger), and if it had to be added, then that it will be removed
228)     upon exiting the context.
229) 
230)     Reentrant, but not thread safe, because it temporarily modifies
231)     global state.
232) 
233)     """
234) 
235)     def __init__(
236)         self,
237)         handler: logging.Handler,
238)         root_logger: str | None = None,
239)     ) -> None:
240)         self.handler = handler
241)         self.root_logger_name = root_logger
242)         self.base_logger = logging.getLogger(self.root_logger_name)
243)         self.action_required: MutableSequence[bool] = collections.deque()
244) 
245)     def __enter__(self) -> Self:
246)         self.action_required.append(
247)             self.handler not in self.base_logger.handlers
248)         )
249)         if self.action_required[-1]:
250)             self.base_logger.addHandler(self.handler)
251)         return self
252) 
253)     def __exit__(
254)         self,
255)         exc_type: type[BaseException] | None,
256)         exc_value: BaseException | None,
257)         exc_tb: types.TracebackType | None,
258)     ) -> Literal[False]:
259)         if self.action_required[-1]:
260)             self.base_logger.removeHandler(self.handler)
261)         self.action_required.pop()
262)         return False
263) 
264) 
265) class StandardWarningsLoggingContextManager(StandardLoggingContextManager):
266)     """A reentrant context manager setting up standard warnings logging.
267) 
268)     Ensures that warnings are being diverted to the logging system, and
269)     that the given handler (defaulting to the CLI logging handler) is
270)     added to the warnings logger. If the handler had to be added, then
271)     it will be removed upon exiting the context.
272) 
273)     Reentrant, but not thread safe, because it temporarily modifies
274)     global state.
275) 
276)     """
277) 
278)     def __init__(
279)         self,
280)         handler: logging.Handler,
281)     ) -> None:
282)         super().__init__(handler=handler, root_logger='py.warnings')
283)         self.stack: MutableSequence[
284)             tuple[
285)                 Callable[
286)                     [
287)                         type[BaseException] | None,
288)                         BaseException | None,
289)                         types.TracebackType | None,
290)                     ],
291)                     None,
292)                 ],
293)                 Callable[
294)                     [
295)                         str | Warning,
296)                         type[Warning],
297)                         str,
298)                         int,
299)                         TextIO | None,
300)                         str | None,
301)                     ],
302)                     None,
303)                 ],
304)             ]
305)         ] = collections.deque()
306) 
307)     def __enter__(self) -> Self:
308)         def showwarning(  # noqa: PLR0913,PLR0917
309)             message: str | Warning,
310)             category: type[Warning],
311)             filename: str,
312)             lineno: int,
313)             file: TextIO | None = None,
314)             line: str | None = None,
315)         ) -> None:
316)             if file is not None:  # pragma: no cover
317)                 self.stack[0][1](
318)                     message, category, filename, lineno, file, line
319)                 )
320)             else:
321)                 logging.getLogger('py.warnings').warning(
322)                     str(
323)                         warnings.formatwarning(
324)                             message, category, filename, lineno, line
325)                         )
326)                     )
327)                 )
328) 
329)         ctx = warnings.catch_warnings()
330)         exit_func = ctx.__exit__
331)         ctx.__enter__()
332)         self.stack.append((exit_func, warnings.showwarning))
333)         warnings.showwarning = showwarning
334)         return super().__enter__()
335) 
336)     def __exit__(
337)         self,
338)         exc_type: type[BaseException] | None,
339)         exc_value: BaseException | None,
340)         exc_tb: types.TracebackType | None,
341)     ) -> Literal[False]:
342)         ret = super().__exit__(exc_type, exc_value, exc_tb)
343)         val = self.stack.pop()[0](exc_type, exc_value, exc_tb)
344)         assert not val
345)         return ret
346) 
347) 
348) P = ParamSpec('P')
349) R = TypeVar('R')
350) 
351) 
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

352) def adjust_logging_level(
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

353)     ctx: click.Context,
354)     /,
355)     param: click.Parameter | None = None,
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

356)     value: int | None = None,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

357) ) -> None:
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

358)     """Change the logs that are emitted to standard error.
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

359) 
360)     This modifies the [`StandardCLILogging`][] settings such that log
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

361)     records at the respective level are emitted, based on the `param`
362)     and the `value`.
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

363) 
364)     """
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

365)     # Note: If multiple options use this callback, then we will be
366)     # called multiple times.  Ensure the runs are idempotent.
367)     if param is None or value is None or ctx.resilient_parsing:
368)         return
369)     StandardCLILogging.cli_handler.setLevel(value)
370)     logging.getLogger(StandardCLILogging.package_name).setLevel(value)
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

371) 
372) 
Marco Ricci Shift option parsing and gr...

Marco Ricci authored 1 month ago

373) # Option parsing and grouping
374) # ===========================
375) 
376) 
377) class OptionGroupOption(click.Option):
378)     """A [`click.Option`][] with an associated group name and group epilog.
379) 
380)     Used by [`CommandWithHelpGroups`][] to print help sections.  Each
381)     subclass contains its own group name and epilog.
382) 
383)     Attributes:
384)         option_group_name:
385)             The name of the option group.  Used as a heading on the help
386)             text for options in this section.
387)         epilog:
388)             An epilog to print after listing the options in this
389)             section.
390) 
391)     """
392) 
393)     option_group_name: str = ''
394)     """"""
395)     epilog: str = ''
396)     """"""
397) 
398)     def __init__(self, *args: Any, **kwargs: Any) -> None:  # noqa: ANN401
399)         if self.__class__ == __class__:  # type: ignore[name-defined]
400)             raise NotImplementedError
401)         super().__init__(*args, **kwargs)
402) 
403) 
404) class CommandWithHelpGroups(click.Command):
405)     """A [`click.Command`][] with support for help/option groups.
406) 
407)     Inspired by [a comment on `pallets/click#373`][CLICK_ISSUE], and
408)     further modified to support group epilogs.
409) 
410)     [CLICK_ISSUE]: https://github.com/pallets/click/issues/373#issuecomment-515293746
411) 
412)     """
413) 
414)     def format_options(
415)         self,
416)         ctx: click.Context,
417)         formatter: click.HelpFormatter,
418)     ) -> None:
419)         r"""Format options on the help listing, grouped into sections.
420) 
421)         This is a callback for [`click.Command.get_help`][] that
422)         implements the `--help` listing, by calling appropriate methods
423)         of the `formatter`.  We list all options (like the base
424)         implementation), but grouped into sections according to the
425)         concrete [`click.Option`][] subclass being used.  If the option
426)         is an instance of some subclass of [`OptionGroupOption`][], then
427)         the section heading and the epilog are taken from the
428)         [`option_group_name`] [OptionGroupOption.option_group_name] and
429)         [`epilog`] [OptionGroupOption.epilog] attributes; otherwise, the
430)         section heading is "Options" (or "Other options" if there are
431)         other option groups) and the epilog is empty.
432) 
433)         Args:
434)             ctx:
435)                 The click context.
436)             formatter:
437)                 The formatter for the `--help` listing.
438) 
439)         """
440)         help_records: dict[str, list[tuple[str, str]]] = {}
441)         epilogs: dict[str, str] = {}
442)         params = self.params[:]
443)         if (  # pragma: no branch
444)             (help_opt := self.get_help_option(ctx)) is not None
445)             and help_opt not in params
446)         ):
447)             params.append(help_opt)
448)         for param in params:
449)             rec = param.get_help_record(ctx)
450)             if rec is not None:
451)                 if isinstance(param, OptionGroupOption):
452)                     group_name = param.option_group_name
453)                     epilogs.setdefault(group_name, param.epilog)
454)                 else:
455)                     group_name = ''
456)                 help_records.setdefault(group_name, []).append(rec)
457)         default_group = help_records.pop('')
458)         default_group_name = (
459)             'Other Options' if len(default_group) > 1 else 'Options'
460)         )
461)         help_records[default_group_name] = default_group
462)         for group_name, records in help_records.items():
463)             with formatter.section(group_name):
464)                 formatter.write_dl(records)
465)             epilog = inspect.cleandoc(epilogs.get(group_name, ''))
466)             if epilog:
467)                 formatter.write_paragraph()
468)                 with formatter.indentation():
469)                     formatter.write_text(epilog)
470) 
471) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

472) class LoggingOption(OptionGroupOption):
473)     """Logging options for the CLI."""
474) 
475)     option_group_name = 'Logging'
476)     epilog = ''
477) 
478) 
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

479) debug_option = click.option(
480)     '--debug',
481)     'logging_level',
482)     is_flag=True,
483)     flag_value=logging.DEBUG,
484)     expose_value=False,
485)     callback=adjust_logging_level,
486)     help='also emit debug information (implies --verbose)',
487)     cls=LoggingOption,
488) )
489) verbose_option = click.option(
490)     '-v',
491)     '--verbose',
492)     'logging_level',
493)     is_flag=True,
494)     flag_value=logging.INFO,
495)     expose_value=False,
496)     callback=adjust_logging_level,
497)     help='emit extra/progress information to standard error',
498)     cls=LoggingOption,
499) )
500) quiet_option = click.option(
501)     '-q',
502)     '--quiet',
503)     'logging_level',
504)     is_flag=True,
505)     flag_value=logging.ERROR,
506)     expose_value=False,
507)     callback=adjust_logging_level,
508)     help='suppress even warnings, emit only errors',
509)     cls=LoggingOption,
510) )
511) 
512) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

513) def standard_logging_options(f: Callable[P, R]) -> Callable[P, R]:
514)     """Decorate the function with standard logging click options.
515) 
516)     Adds the three click options `-v`/`--verbose`, `-q`/`--quiet` and
517)     `--debug`, which issue callbacks to the [`log_info`][],
518)     [`silence_warnings`][] and [`log_debug`][] functions, respectively.
519) 
520)     Args:
521)         f: A callable to decorate.
522) 
523)     Returns:
524)         The decorated callable.
525) 
526)     """
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

527)     return debug_option(verbose_option(quiet_option(f)))
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

528) 
529) 
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

530) # Top-level
531) # =========
532) 
533) 
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

534) class _DefaultToVaultGroup(click.Group):
535)     """A helper class to implement the default-to-"vault"-subcommand behavior.
536) 
537)     Modifies internal [`click.MultiCommand`][] methods, and thus is both
538)     an implementation detail and a kludge.
539) 
540)     """
541) 
542)     def resolve_command(
543)         self, ctx: click.Context, args: list[str]
544)     ) -> tuple[str | None, click.Command | None, list[str]]:
545)         """Resolve a command, but default to "vault" instead of erroring out.
546) 
547)         Based on code from click 8.1, which appears to be essentially
548)         untouched since at least click 3.2.
549) 
550)         """
551)         cmd_name = click.utils.make_str(args[0])
552) 
553)         # ORIGINAL COMMENT
554)         # Get the command
555)         cmd = self.get_command(ctx, cmd_name)
556) 
557)         # ORIGINAL COMMENT
558)         # If we can't find the command but there is a normalization
559)         # function available, we try with that one.
560)         if (  # pragma: no cover
561)             cmd is None and ctx.token_normalize_func is not None
562)         ):
563)             cmd_name = ctx.token_normalize_func(cmd_name)
564)             cmd = self.get_command(ctx, cmd_name)
565) 
566)         # ORIGINAL COMMENT
567)         # If we don't find the command we want to show an error message
568)         # to the user that it was not provided.  However, there is
569)         # something else we should do: if the first argument looks like
570)         # an option we want to kick off parsing again for arguments to
571)         # resolve things like --help which now should go to the main
572)         # place.
573)         if cmd is None and not ctx.resilient_parsing:
574)             if click.parser.split_opt(cmd_name)[0]:
575)                 self.parse_args(ctx, ctx.args)
576)             # Instead of calling ctx.fail here, default to "vault", and
577)             # issue a deprecation warning.
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

578)             logger = logging.getLogger(PROG_NAME)
579)             deprecation = logging.getLogger(f'{PROG_NAME}.deprecation')
580)             deprecation.warning(
581)                 'A subcommand will be required in v1.0. '
582)                 'See --help for available subcommands.'
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

583)             )
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

584)             logger.warning('Defaulting to subcommand "vault".')
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

585)             cmd_name = 'vault'
586)             cmd = self.get_command(ctx, cmd_name)
587)             assert cmd is not None, 'Mandatory subcommand "vault" missing!'
588)             args = [cmd_name, *args]
589)         return cmd_name if cmd else None, cmd, args[1:]  # noqa: DOC201
590) 
591) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

592) class _TopLevelCLIEntryPoint(_DefaultToVaultGroup):
593)     """A minor variation of _DefaultToVaultGroup for the top-level command.
594) 
595)     When called as a function, this sets up the environment properly
596)     before invoking the actual callbacks.  Currently, this means setting
597)     up the logging subsystem and the delegation of Python warnings to
598)     the logging subsystem.
599) 
600)     The environment setup can be bypassed by calling the `.main` method
601)     directly.
602) 
603)     """
604) 
605)     def __call__(  # pragma: no cover
606)         self,
607)         *args: Any,  # noqa: ANN401
608)         **kwargs: Any,  # noqa: ANN401
609)     ) -> Any:  # noqa: ANN401
610)         """"""  # noqa: D419
611)         # Coverage testing is done with the `click.testing` module,
612)         # which does not use the `__call__` shortcut.  So it is normal
613)         # that this function is never called, and thus should be
614)         # excluded from coverage.
615)         with (
616)             StandardCLILogging.ensure_standard_logging(),
617)             StandardCLILogging.ensure_standard_warnings_logging(),
618)         ):
619)             return self.main(*args, **kwargs)
620) 
621) 
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

622) @click.group(
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

623)     context_settings={
624)         'help_option_names': ['-h', '--help'],
625)         'ignore_unknown_options': True,
626)         'allow_interspersed_args': False,
627)     },
628)     epilog=r"""
629)         Configuration is stored in a directory according to the
630)         DERIVEPASSPHRASE_PATH variable, which defaults to
631)         `~/.derivepassphrase` on UNIX-like systems and
632)         `C:\Users\<user>\AppData\Roaming\Derivepassphrase` on Windows.
Marco Ricci Fix minor typo, formatting...

Marco Ricci authored 3 months ago

633)     """,
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

634)     invoke_without_command=True,
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

635)     cls=_TopLevelCLIEntryPoint,
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

636) )
637) @click.version_option(version=dpp.__version__, prog_name=PROG_NAME)
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

638) @standard_logging_options
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

639) @click.pass_context
640) def derivepassphrase(ctx: click.Context, /) -> None:
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

641)     """Derive a strong passphrase, deterministically, from a master secret.
642) 
643)     Using a master secret, derive a passphrase for a named service,
644)     subject to constraints e.g. on passphrase length, allowed
645)     characters, etc.  The exact derivation depends on the selected
646)     derivation scheme.  For each scheme, it is computationally
647)     infeasible to discern the master secret from the derived passphrase.
648)     The derivations are also deterministic, given the same inputs, thus
649)     the resulting passphrases need not be stored explicitly.  The
650)     service name and constraints themselves also generally need not be
651)     kept secret, depending on the scheme.
652) 
653)     The currently implemented subcommands are "vault" (for the scheme
654)     used by vault) and "export" (for exporting foreign configuration
655)     data).  See the respective `--help` output for instructions.  If no
656)     subcommand is given, we default to "vault".
657) 
658)     Deprecation notice: Defaulting to "vault" is deprecated.  Starting
659)     in v1.0, the subcommand must be specified explicitly.\f
660) 
661)     This is a [`click`][CLICK]-powered command-line interface function,
662)     and not intended for programmatic use.  Call with arguments
663)     `['--help']` to see full documentation of the interface.  (See also
664)     [`click.testing.CliRunner`][] for controlled, programmatic
665)     invocation.)
666) 
Marco Ricci Update all URLs to stable a...

Marco Ricci authored 3 months ago

667)     [CLICK]: https://pypi.org/package/click/
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

668) 
669)     """  # noqa: D301
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

670)     logger = logging.getLogger(PROG_NAME)
671)     deprecation = logging.getLogger(f'{PROG_NAME}.deprecation')
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

672)     if ctx.invoked_subcommand is None:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

673)         deprecation.warning(
674)             'A subcommand will be required in v1.0. '
675)             'See --help for available subcommands.'
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

676)         )
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

677)         logger.warning('Defaulting to subcommand "vault".')
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

678)         # See definition of click.Group.invoke, non-chained case.
679)         with ctx:
680)             sub_ctx = derivepassphrase_vault.make_context(
681)                 'vault', ctx.args, parent=ctx
682)             )
683)             with sub_ctx:
684)                 return derivepassphrase_vault.invoke(sub_ctx)
685)     return None
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

686) 
687) 
688) # Exporter
689) # ========
690) 
691) 
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

692) @derivepassphrase.group(
693)     'export',
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

694)     context_settings={
695)         'help_option_names': ['-h', '--help'],
696)         'ignore_unknown_options': True,
697)         'allow_interspersed_args': False,
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

698)     },
699)     invoke_without_command=True,
700)     cls=_DefaultToVaultGroup,
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

701) )
702) @click.version_option(version=dpp.__version__, prog_name=PROG_NAME)
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

703) @standard_logging_options
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

704) @click.pass_context
705) def derivepassphrase_export(ctx: click.Context, /) -> None:
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

706)     """Export a foreign configuration to standard output.
707) 
708)     Read a foreign system configuration, extract all information from
709)     it, and export the resulting configuration to standard output.
710) 
711)     The only available subcommand is "vault", which implements the
712)     vault-native configuration scheme.  If no subcommand is given, we
713)     default to "vault".
714) 
715)     Deprecation notice: Defaulting to "vault" is deprecated.  Starting
716)     in v1.0, the subcommand must be specified explicitly.\f
717) 
718)     This is a [`click`][CLICK]-powered command-line interface function,
719)     and not intended for programmatic use.  Call with arguments
720)     `['--help']` to see full documentation of the interface.  (See also
721)     [`click.testing.CliRunner`][] for controlled, programmatic
722)     invocation.)
723) 
Marco Ricci Update all URLs to stable a...

Marco Ricci authored 3 months ago

724)     [CLICK]: https://pypi.org/package/click/
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

725) 
726)     """  # noqa: D301
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

727)     logger = logging.getLogger(PROG_NAME)
728)     deprecation = logging.getLogger(f'{PROG_NAME}.deprecation')
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

729)     if ctx.invoked_subcommand is None:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

730)         deprecation.warning(
731)             'A subcommand will be required in v1.0. '
732)             'See --help for available subcommands.'
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

733)         )
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

734)         logger.warning('Defaulting to subcommand "vault".')
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

735)         # See definition of click.Group.invoke, non-chained case.
736)         with ctx:
737)             sub_ctx = derivepassphrase_export_vault.make_context(
738)                 'vault', ctx.args, parent=ctx
739)             )
740)             # Constructing the subcontext above will usually already
741)             # lead to a click.UsageError, so this block typically won't
742)             # actually be called.
743)             with sub_ctx:  # pragma: no cover
744)                 return derivepassphrase_export_vault.invoke(sub_ctx)
745)     return None
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

746) 
747) 
748) def _load_data(
749)     fmt: Literal['v0.2', 'v0.3', 'storeroom'],
750)     path: str | bytes | os.PathLike[str],
751)     key: bytes,
752) ) -> Any:  # noqa: ANN401
753)     contents: bytes
754)     module: types.ModuleType
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

755)     # Use match/case here once Python 3.9 becomes unsupported.
756)     if fmt == 'v0.2':
757)         module = importlib.import_module(
758)             'derivepassphrase.exporter.vault_native'
759)         )
760)         if module.STUBBED:
761)             raise ModuleNotFoundError
762)         with open(path, 'rb') as infile:
763)             contents = base64.standard_b64decode(infile.read())
764)         return module.export_vault_native_data(
765)             contents, key, try_formats=['v0.2']
766)         )
767)     elif fmt == 'v0.3':  # noqa: RET505
768)         module = importlib.import_module(
769)             'derivepassphrase.exporter.vault_native'
770)         )
771)         if module.STUBBED:
772)             raise ModuleNotFoundError
773)         with open(path, 'rb') as infile:
774)             contents = base64.standard_b64decode(infile.read())
775)         return module.export_vault_native_data(
776)             contents, key, try_formats=['v0.3']
777)         )
778)     elif fmt == 'storeroom':
779)         module = importlib.import_module('derivepassphrase.exporter.storeroom')
780)         if module.STUBBED:
781)             raise ModuleNotFoundError
782)         return module.export_storeroom_data(path, key)
783)     else:  # pragma: no cover
784)         assert_never(fmt)
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

785) 
786) 
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

787) @derivepassphrase_export.command(
788)     'vault',
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

789)     context_settings={'help_option_names': ['-h', '--help']},
790) )
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

791) @standard_logging_options
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

792) @click.option(
793)     '-f',
794)     '--format',
795)     'formats',
796)     metavar='FMT',
797)     multiple=True,
798)     default=('v0.3', 'v0.2', 'storeroom'),
799)     type=click.Choice(['v0.2', 'v0.3', 'storeroom']),
800)     help='try the following storage formats, in order (default: v0.3, v0.2)',
801) )
802) @click.option(
803)     '-k',
804)     '--key',
805)     metavar='K',
806)     help=(
807)         'use K as the storage master key '
808)         '(default: check the `VAULT_KEY`, `LOGNAME`, `USER` or '
809)         '`USERNAME` environment variables)'
810)     ),
811) )
812) @click.argument('path', metavar='PATH', required=True)
813) @click.pass_context
814) def derivepassphrase_export_vault(
815)     ctx: click.Context,
816)     /,
817)     *,
818)     path: str | bytes | os.PathLike[str],
819)     formats: Sequence[Literal['v0.2', 'v0.3', 'storeroom']] = (),
820)     key: str | bytes | None = None,
821) ) -> None:
822)     """Export a vault-native configuration to standard output.
823) 
824)     Read the vault-native configuration at PATH, extract all information
825)     from it, and export the resulting configuration to standard output.
826)     Depending on the configuration format, PATH may either be a file or
827)     a directory.  Supports the vault "v0.2", "v0.3" and "storeroom"
828)     formats.
829) 
830)     If PATH is explicitly given as `VAULT_PATH`, then use the
831)     `VAULT_PATH` environment variable to determine the correct path.
832)     (Use `./VAULT_PATH` or similar to indicate a file/directory actually
833)     named `VAULT_PATH`.)
834) 
835)     """
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

836)     logger = logging.getLogger(PROG_NAME)
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

837)     if path in {'VAULT_PATH', b'VAULT_PATH'}:
838)         path = exporter.get_vault_path()
839)     if key is None:
840)         key = exporter.get_vault_key()
841)     elif isinstance(key, str):  # pragma: no branch
842)         key = key.encode('utf-8')
843)     for fmt in formats:
844)         try:
845)             config = _load_data(fmt, path, key)
846)         except (
847)             IsADirectoryError,
848)             NotADirectoryError,
849)             ValueError,
850)             RuntimeError,
851)         ):
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

852)             logger.info('Cannot load as %s: %s', fmt, path)
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

853)             continue
854)         except OSError as exc:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

855)             logger.error(
856)                 'Cannot parse %r as a valid config: %s: %r',
857)                 path,
858)                 exc.strerror,
859)                 exc.filename,
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

860)             )
861)             ctx.exit(1)
862)         except ModuleNotFoundError:
863)             # TODO(the-13th-letter): Use backslash continuation.
864)             # https://github.com/nedbat/coveragepy/issues/1836
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

865)             logger.error(
866)                 'Cannot load the required Python module "cryptography".'
867)             )
868)             logger.info('pip users: see the "export" extra.')
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

869)             ctx.exit(1)
870)         else:
871)             if not _types.is_vault_config(config):
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

872)                 logger.error('Invalid vault config: %r', config)
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

873)                 ctx.exit(1)
874)             click.echo(json.dumps(config, indent=2, sort_keys=True))
875)             break
876)     else:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

877)         logger.error('Cannot parse %r as a valid config.', path)
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

878)         ctx.exit(1)
879) 
880) 
881) # Vault
882) # =====
883) 
884) 
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

885) def _config_filename(
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

886)     subsystem: str | None = 'old settings.json',
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

887) ) -> str | bytes | pathlib.Path:
888)     """Return the filename of the configuration file for the subsystem.
889) 
890)     The (implicit default) file is currently named `settings.json`,
891)     located within the configuration directory as determined by the
892)     `DERIVEPASSPHRASE_PATH` environment variable, or by
893)     [`click.get_app_dir`][] in POSIX mode.  Depending on the requested
894)     subsystem, this will usually be a different file within that
895)     directory.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

896) 
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

897)     Args:
898)         subsystem:
899)             Name of the configuration subsystem whose configuration
900)             filename to return.  If not given, return the old filename
901)             from before the subcommand migration.  If `None`, return the
902)             configuration directory instead.
903) 
904)     Raises:
905)         AssertionError:
906)             An unknown subsystem was passed.
907) 
908)     Deprecated:
909)         Since v0.2.0: The implicit default subsystem and the old
910)         configuration filename are deprecated, and will be removed in v1.0.
911)         The subsystem will be mandatory to specify.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

912) 
913)     """
914)     path: str | bytes | pathlib.Path
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

915)     path = os.getenv(PROG_NAME.upper() + '_PATH') or click.get_app_dir(
916)         PROG_NAME, force_posix=True
917)     )
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

918)     # Use match/case here once Python 3.9 becomes unsupported.
919)     if subsystem is None:
920)         return path
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

921)     elif subsystem == 'vault':  # noqa: RET505
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

922)         filename = f'{subsystem}.json'
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

923)     elif subsystem == 'user configuration':
924)         filename = 'config.toml'
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

925)     elif subsystem == 'old settings.json':
926)         filename = 'settings.json'
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

927)     else:  # pragma: no cover
928)         msg = f'Unknown configuration subsystem: {subsystem!r}'
929)         raise AssertionError(msg)
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

930)     return os.path.join(path, filename)
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

931) 
932) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

933) def _load_config() -> _types.VaultConfig:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

934)     """Load a vault(1)-compatible config from the application directory.
935) 
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

936)     The filename is obtained via [`_config_filename`][].  This must be
937)     an unencrypted JSON file.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

938) 
939)     Returns:
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

940)         The vault settings.  See [`_types.VaultConfig`][] for details.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

941) 
942)     Raises:
943)         OSError:
944)             There was an OS error accessing the file.
945)         ValueError:
946)             The data loaded from the file is not a vault(1)-compatible
947)             config.
948) 
949)     """
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

950)     filename = _config_filename(subsystem='vault')
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

951)     with open(filename, 'rb') as fileobj:
952)         data = json.load(fileobj)
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

953)     if not _types.is_vault_config(data):
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

954)         raise ValueError(_INVALID_VAULT_CONFIG)
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

955)     return data
956) 
957) 
Marco Ricci Permit one flaky test and f...

Marco Ricci authored 2 months ago

958) def _migrate_and_load_old_config() -> tuple[
959)     _types.VaultConfig, OSError | None
960) ]:
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

961)     """Load and migrate a vault(1)-compatible config.
962) 
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

963)     The (old) filename is obtained via [`_config_filename`][].  This
964)     must be an unencrypted JSON file.  After loading, the file is
965)     migrated to the new standard filename.
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

966) 
967)     Returns:
968)         The vault settings, and an optional exception encountered during
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

969)         migration.  See [`_types.VaultConfig`][] for details on the
970)         former.
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

971) 
972)     Raises:
973)         OSError:
974)             There was an OS error accessing the old file.
975)         ValueError:
976)             The data loaded from the file is not a vault(1)-compatible
977)             config.
978) 
979)     """
980)     new_filename = _config_filename(subsystem='vault')
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

981)     old_filename = _config_filename(subsystem='old settings.json')
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

982)     with open(old_filename, 'rb') as fileobj:
983)         data = json.load(fileobj)
984)     if not _types.is_vault_config(data):
985)         raise ValueError(_INVALID_VAULT_CONFIG)
986)     try:
987)         os.replace(old_filename, new_filename)
988)     except OSError as exc:
989)         return data, exc
990)     else:
991)         return data, None
992) 
993) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

994) def _save_config(config: _types.VaultConfig, /) -> None:
Marco Ricci Create the configuration di...

Marco Ricci authored 5 months ago

995)     """Save a vault(1)-compatible config to the application directory.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

996) 
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

997)     The filename is obtained via [`_config_filename`][].  The config
998)     will be stored as an unencrypted JSON file.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

999) 
1000)     Args:
1001)         config:
1002)             vault configuration to save.
1003) 
1004)     Raises:
1005)         OSError:
1006)             There was an OS error accessing or writing the file.
1007)         ValueError:
1008)             The data cannot be stored as a vault(1)-compatible config.
1009) 
1010)     """
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

1011)     if not _types.is_vault_config(config):
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1012)         raise ValueError(_INVALID_VAULT_CONFIG)
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1013)     filename = _config_filename(subsystem='vault')
Marco Ricci Create the configuration di...

Marco Ricci authored 5 months ago

1014)     filedir = os.path.dirname(os.path.abspath(filename))
1015)     try:
1016)         os.makedirs(filedir, exist_ok=False)
1017)     except FileExistsError:
1018)         if not os.path.isdir(filedir):
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1019)             raise  # noqa: DOC501
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1020)     with open(filename, 'w', encoding='UTF-8') as fileobj:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1021)         json.dump(config, fileobj)
1022) 
1023) 
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

1024) def _load_user_config() -> dict[str, Any]:
1025)     """Load the user config from the application directory.
1026) 
1027)     The filename is obtained via [`_config_filename`][].
1028) 
1029)     Returns:
1030)         The user configuration, as a nested `dict`.
1031) 
1032)     Raises:
1033)         OSError:
1034)             There was an OS error accessing the file.
1035)         ValueError:
1036)             The data loaded from the file is not a valid configuration
1037)             file.
1038) 
1039)     """
1040)     filename = _config_filename(subsystem='user configuration')
1041)     with open(filename, 'rb') as fileobj:
1042)         return tomllib.load(fileobj)
1043) 
1044) 
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1045) def _get_suitable_ssh_keys(
Marco Ricci Move `sequin` and `ssh_agen...

Marco Ricci authored 5 months ago

1046)     conn: ssh_agent.SSHAgentClient | socket.socket | None = None, /
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

1047) ) -> Iterator[_types.KeyCommentPair]:
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1048)     """Yield all SSH keys suitable for passphrase derivation.
1049) 
1050)     Suitable SSH keys are queried from the running SSH agent (see
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

1051)     [`ssh_agent.SSHAgentClient.list_keys`][]).
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1052) 
1053)     Args:
1054)         conn:
Marco Ricci Support one-off SSH agent c...

Marco Ricci authored 1 month ago

1055)             An optional connection hint to the SSH agent.  See
1056)             [`ssh_agent.SSHAgentClient.ensure_agent_subcontext`][].
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1057) 
1058)     Yields:
Marco Ricci Convert old syntax for Yiel...

Marco Ricci authored 3 months ago

1059)         Every SSH key from the SSH agent that is suitable for passphrase
1060)         derivation.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1061) 
1062)     Raises:
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1063)         KeyError:
1064)             `conn` was `None`, and the `SSH_AUTH_SOCK` environment
1065)             variable was not found.
Marco Ricci Fail gracefully if UNIX dom...

Marco Ricci authored 3 months ago

1066)         NotImplementedError:
1067)             `conn` was `None`, and this Python does not support
1068)             [`socket.AF_UNIX`][], so the SSH agent client cannot be
1069)             automatically set up.
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1070)         OSError:
1071)             `conn` was a socket or `None`, and there was an error
1072)             setting up a socket connection to the agent.
Marco Ricci Distinguish between a key l...

Marco Ricci authored 6 months ago

1073)         LookupError:
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1074)             No keys usable for passphrase derivation are loaded into the
1075)             SSH agent.
Marco Ricci Distinguish between a key l...

Marco Ricci authored 6 months ago

1076)         RuntimeError:
1077)             There was an error communicating with the SSH agent.
Marco Ricci Fix miscellaneous small doc...

Marco Ricci authored 3 months ago

1078)         ssh_agent.SSHAgentFailedError:
Marco Ricci Add a specific error class...

Marco Ricci authored 4 months ago

1079)             The agent failed to supply a list of loaded keys.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1080) 
1081)     """
Marco Ricci Support one-off SSH agent c...

Marco Ricci authored 1 month ago

1082)     with ssh_agent.SSHAgentClient.ensure_agent_subcontext(conn) as client:
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1083)         try:
1084)             all_key_comment_pairs = list(client.list_keys())
1085)         except EOFError as e:  # pragma: no cover
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1086)             raise RuntimeError(_AGENT_COMMUNICATION_ERROR) from e
Marco Ricci Publish polished `is_suitab...

Marco Ricci authored 1 month ago

1087)         suitable_keys = copy.copy(all_key_comment_pairs)
1088)         for pair in all_key_comment_pairs:
1089)             key, _comment = pair
1090)             if vault.Vault.is_suitable_ssh_key(key, client=client):
1091)                 yield pair
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1092)     if not suitable_keys:  # pragma: no cover
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1093)         raise LookupError(_NO_USABLE_KEYS)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1094) 
1095) 
1096) def _prompt_for_selection(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1097)     items: Sequence[str | bytes],
1098)     heading: str = 'Possible choices:',
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1099)     single_choice_prompt: str = 'Confirm this choice?',
1100) ) -> int:
1101)     """Prompt user for a choice among the given items.
1102) 
1103)     Print the heading, if any, then present the items to the user.  If
1104)     there are multiple items, prompt the user for a selection, validate
1105)     the choice, then return the list index of the selected item.  If
1106)     there is only a single item, request confirmation for that item
1107)     instead, and return the correct index.
1108) 
1109)     Args:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1110)         items:
1111)             The list of items to choose from.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1112)         heading:
1113)             A heading for the list of items, to print immediately
1114)             before.  Defaults to a reasonable standard heading.  If
1115)             explicitly empty, print no heading.
1116)         single_choice_prompt:
1117)             The confirmation prompt if there is only a single possible
1118)             choice.  Defaults to a reasonable standard prompt.
1119) 
1120)     Returns:
1121)         An index into the items sequence, indicating the user's
1122)         selection.
1123) 
1124)     Raises:
1125)         IndexError:
1126)             The user made an invalid or empty selection, or requested an
1127)             abort.
1128) 
1129)     """
1130)     n = len(items)
1131)     if heading:
1132)         click.echo(click.style(heading, bold=True))
1133)     for i, x in enumerate(items, start=1):
1134)         click.echo(click.style(f'[{i}]', bold=True), nl=False)
1135)         click.echo(' ', nl=False)
1136)         click.echo(x)
1137)     if n > 1:
1138)         choices = click.Choice([''] + [str(i) for i in range(1, n + 1)])
1139)         choice = click.prompt(
1140)             f'Your selection? (1-{n}, leave empty to abort)',
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1141)             err=True,
1142)             type=choices,
1143)             show_choices=False,
1144)             show_default=False,
1145)             default='',
1146)         )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1147)         if not choice:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1148)             raise IndexError(_EMPTY_SELECTION)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1149)         return int(choice) - 1
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1150)     prompt_suffix = (
1151)         ' ' if single_choice_prompt.endswith(tuple('?.!')) else ': '
1152)     )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1153)     try:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1154)         click.confirm(
1155)             single_choice_prompt,
1156)             prompt_suffix=prompt_suffix,
1157)             err=True,
1158)             abort=True,
1159)             default=False,
1160)             show_default=False,
1161)         )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1162)     except click.Abort:
1163)         raise IndexError(_EMPTY_SELECTION) from None
1164)     return 0
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1165) 
1166) 
1167) def _select_ssh_key(
Marco Ricci Move `sequin` and `ssh_agen...

Marco Ricci authored 5 months ago

1168)     conn: ssh_agent.SSHAgentClient | socket.socket | None = None, /
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1169) ) -> bytes | bytearray:
1170)     """Interactively select an SSH key for passphrase derivation.
1171) 
1172)     Suitable SSH keys are queried from the running SSH agent (see
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

1173)     [`ssh_agent.SSHAgentClient.list_keys`][]), then the user is prompted
1174)     interactively (see [`click.prompt`][]) for a selection.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1175) 
1176)     Args:
1177)         conn:
Marco Ricci Support one-off SSH agent c...

Marco Ricci authored 1 month ago

1178)             An optional connection hint to the SSH agent.  See
1179)             [`ssh_agent.SSHAgentClient.ensure_agent_subcontext`][].
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1180) 
1181)     Returns:
1182)         The selected SSH key.
1183) 
1184)     Raises:
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1185)         KeyError:
1186)             `conn` was `None`, and the `SSH_AUTH_SOCK` environment
1187)             variable was not found.
Marco Ricci Fail gracefully if UNIX dom...

Marco Ricci authored 3 months ago

1188)         NotImplementedError:
1189)             `conn` was `None`, and this Python does not support
1190)             [`socket.AF_UNIX`][], so the SSH agent client cannot be
1191)             automatically set up.
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1192)         OSError:
1193)             `conn` was a socket or `None`, and there was an error
1194)             setting up a socket connection to the agent.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1195)         IndexError:
1196)             The user made an invalid or empty selection, or requested an
1197)             abort.
Marco Ricci Distinguish between a key l...

Marco Ricci authored 6 months ago

1198)         LookupError:
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1199)             No keys usable for passphrase derivation are loaded into the
1200)             SSH agent.
Marco Ricci Distinguish between a key l...

Marco Ricci authored 6 months ago

1201)         RuntimeError:
1202)             There was an error communicating with the SSH agent.
Marco Ricci Add a specific error class...

Marco Ricci authored 4 months ago

1203)         SSHAgentFailedError:
1204)             The agent failed to supply a list of loaded keys.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1205)     """
1206)     suitable_keys = list(_get_suitable_ssh_keys(conn))
1207)     key_listing: list[str] = []
Marco Ricci Move `sequin` and `ssh_agen...

Marco Ricci authored 5 months ago

1208)     unstring_prefix = ssh_agent.SSHAgentClient.unstring_prefix
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1209)     for key, comment in suitable_keys:
1210)         keytype = unstring_prefix(key)[0].decode('ASCII')
1211)         key_str = base64.standard_b64encode(key).decode('ASCII')
Marco Ricci Make suitable SSH key listi...

Marco Ricci authored 1 month ago

1212)         remaining_key_display_length = KEY_DISPLAY_LENGTH - 1 - len(keytype)
1213)         key_extract = min(
1214)             key_str,
1215)             '...' + key_str[-remaining_key_display_length:],
1216)             key=len,
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1217)         )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1218)         comment_str = comment.decode('UTF-8', errors='replace')
Marco Ricci Make suitable SSH key listi...

Marco Ricci authored 1 month ago

1219)         key_listing.append(f'{keytype} {key_extract}  {comment_str}')
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1220)     choice = _prompt_for_selection(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1221)         key_listing,
1222)         heading='Suitable SSH keys:',
1223)         single_choice_prompt='Use this key?',
1224)     )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1225)     return suitable_keys[choice].key
1226) 
1227) 
1228) def _prompt_for_passphrase() -> str:
1229)     """Interactively prompt for the passphrase.
1230) 
1231)     Calls [`click.prompt`][] internally.  Moved into a separate function
1232)     mainly for testing/mocking purposes.
1233) 
1234)     Returns:
1235)         The user input.
1236) 
1237)     """
Marco Ricci Fix typing issues in mypy s...

Marco Ricci authored 5 months ago

1238)     return cast(
1239)         str,
1240)         click.prompt(
1241)             'Passphrase',
1242)             default='',
1243)             hide_input=True,
1244)             show_default=False,
1245)             err=True,
1246)         ),
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1247)     )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1248) 
1249) 
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1250) def _toml_key(*parts: str) -> str:
1251)     """Return a formatted TOML key, given its parts."""
1252)     def escape(string: str) -> str:
1253)         translated = string.translate({
1254)             0: r'\u0000',
1255)             1: r'\u0001',
1256)             2: r'\u0002',
1257)             3: r'\u0003',
1258)             4: r'\u0004',
1259)             5: r'\u0005',
1260)             6: r'\u0006',
1261)             7: r'\u0007',
1262)             8: r'\b',
1263)             9: r'\t',
1264)             10: r'\n',
1265)             11: r'\u000B',
1266)             12: r'\f',
1267)             13: r'\r',
1268)             14: r'\u000E',
1269)             15: r'\u000F',
1270)             ord('"'): r'\"',
1271)             ord('\\'): r'\\',
1272)             127: r'\u007F',
1273)         })
1274)         return f'"{translated}"' if translated != string else string
1275)     return '.'.join(map(escape, parts))
1276) 
1277) 
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1278) class _ORIGIN(enum.Enum):
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1279)     INTERACTIVE: str = 'interactive input'
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1280) 
1281) 
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1282) def _check_for_misleading_passphrase(
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1283)     key: tuple[str, ...] | _ORIGIN,
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1284)     value: dict[str, Any],
1285)     *,
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1286)     main_config: dict[str, Any],
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1287) ) -> None:
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1288)     form_key = 'unicode-normalization-form'
1289)     default_form: str = main_config.get('vault', {}).get(
1290)         f'default-{form_key}', 'NFC'
1291)     )
1292)     form_dict: dict[str, dict] = main_config.get('vault', {}).get(form_key, {})
1293)     form: Any = (
1294)         default_form
1295)         if isinstance(key, _ORIGIN) or key == ('global',)
1296)         else form_dict.get(key[1], default_form)
1297)     )
1298)     config_key = (
1299)         _toml_key('vault', key[1], form_key)
1300)         if isinstance(key, tuple) and len(key) > 1 and key[1] in form_dict
1301)         else f'vault.default-{form_key}'
1302)     )
1303)     if form not in {'NFC', 'NFD', 'NFKC', 'NFKD'}:
1304)         msg = f'Invalid value {form!r} for config key {config_key}'
1305)         raise AssertionError(msg)
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1306)     logger = logging.getLogger(PROG_NAME)
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1307)     formatted_key = (
1308)         key.value if isinstance(key, _ORIGIN) else _types.json_path(key)
1309)     )
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1310)     if 'phrase' in value:
1311)         phrase = value['phrase']
1312)         if not unicodedata.is_normalized(form, phrase):
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1313)             logger.warning(
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1314)                 (
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1315)                     'the %s passphrase is not %s-normalized.  '
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1316)                     'Make sure to double-check this is really the '
1317)                     'passphrase you want.'
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1318)                 ),
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1319)                 formatted_key,
1320)                 form,
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

1321)                 stacklevel=2,
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

1322)             )
1323) 
1324) 
Marco Ricci Hoist and add tests for int...

Marco Ricci authored 3 weeks ago

1325) def _key_to_phrase(
1326)     key_: str | bytes | bytearray,
1327)     /,
1328)     *,
1329)     error_callback: Callable[..., NoReturn] = sys.exit,
1330) ) -> bytes | bytearray:
1331)     key = base64.standard_b64decode(key_)
1332)     try:
1333)         with ssh_agent.SSHAgentClient.ensure_agent_subcontext() as client:
1334)             try:
1335)                 return vault.Vault.phrase_from_key(key, conn=client)
1336)             except ssh_agent.SSHAgentFailedError as e:
1337)                 try:
1338)                     keylist = client.list_keys()
1339)                 except ssh_agent.SSHAgentFailedError:
1340)                     pass
1341)                 except Exception as e2:  # noqa: BLE001
1342)                     e.__context__ = e2
1343)                 else:
1344)                     if not any(  # pragma: no branch
1345)                         k == key for k, _ in keylist
1346)                     ):
1347)                         error_callback(
1348)                             'The requested SSH key is not loaded '
1349)                             'into the agent.'
1350)                         )
1351)                 error_callback(e)
1352)     except KeyError:
1353)         error_callback('Cannot find running SSH agent; check SSH_AUTH_SOCK')
1354)     except NotImplementedError:
1355)         error_callback(
1356)             'Cannot connect to SSH agent because '
1357)             'this Python version does not support UNIX domain sockets'
1358)         )
1359)     except OSError as e:
1360)         error_callback('Cannot connect to SSH agent: %s', e.strerror)
1361) 
1362) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1363) # Concrete option groups used by this command-line interface.
1364) class PasswordGenerationOption(OptionGroupOption):
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1365)     """Password generation options for the CLI."""
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1366) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1367)     option_group_name = 'Password generation'
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1368)     epilog = """
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1369)         Use NUMBER=0, e.g. "--symbol 0", to exclude a character type
1370)         from the output.
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1371)     """
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1372) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1373) 
1374) class ConfigurationOption(OptionGroupOption):
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1375)     """Configuration options for the CLI."""
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1376) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1377)     option_group_name = 'Configuration'
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1378)     epilog = """
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1379)         Use $VISUAL or $EDITOR to configure the spawned editor.
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1380)     """
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1381) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1382) 
1383) class StorageManagementOption(OptionGroupOption):
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1384)     """Storage management options for the CLI."""
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1385) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1386)     option_group_name = 'Storage management'
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1387)     epilog = """
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1388)         Using "-" as PATH for standard input/standard output is
1389)         supported.
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1390)     """
1391) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1392) 
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1393) class CompatibilityOption(OptionGroupOption):
1394)     """Compatibility and incompatibility options for the CLI."""
1395) 
1396)     option_group_name = 'Options concerning compatibility with other tools'
1397) 
1398) 
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1399) def _validate_occurrence_constraint(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1400)     ctx: click.Context,
1401)     param: click.Parameter,
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1402)     value: Any,  # noqa: ANN401
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1403) ) -> int | None:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1404)     """Check that the occurrence constraint is valid (int, 0 or larger).
1405) 
1406)     Args:
1407)         ctx: The `click` context.
1408)         param: The current command-line parameter.
1409)         value: The parameter value to be checked.
1410) 
1411)     Returns:
1412)         The parsed parameter value.
1413) 
1414)     Raises:
1415)         click.BadParameter: The parameter value is invalid.
1416) 
1417)     """
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1418)     del ctx  # Unused.
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1419)     del param  # Unused.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1420)     if value is None:
1421)         return value
1422)     if isinstance(value, int):
1423)         int_value = value
1424)     else:
1425)         try:
1426)             int_value = int(value, 10)
1427)         except ValueError as e:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1428)             msg = 'not an integer'
1429)             raise click.BadParameter(msg) from e
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1430)     if int_value < 0:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1431)         msg = 'not a non-negative integer'
1432)         raise click.BadParameter(msg)
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1433)     return int_value
1434) 
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1435) 
1436) def _validate_length(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1437)     ctx: click.Context,
1438)     param: click.Parameter,
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1439)     value: Any,  # noqa: ANN401
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1440) ) -> int | None:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1441)     """Check that the length is valid (int, 1 or larger).
1442) 
1443)     Args:
1444)         ctx: The `click` context.
1445)         param: The current command-line parameter.
1446)         value: The parameter value to be checked.
1447) 
1448)     Returns:
1449)         The parsed parameter value.
1450) 
1451)     Raises:
1452)         click.BadParameter: The parameter value is invalid.
1453) 
1454)     """
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1455)     del ctx  # Unused.
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1456)     del param  # Unused.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1457)     if value is None:
1458)         return value
1459)     if isinstance(value, int):
1460)         int_value = value
1461)     else:
1462)         try:
1463)             int_value = int(value, 10)
1464)         except ValueError as e:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1465)             msg = 'not an integer'
1466)             raise click.BadParameter(msg) from e
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1467)     if int_value < 1:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1468)         msg = 'not a positive integer'
1469)         raise click.BadParameter(msg)
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1470)     return int_value
1471) 
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1472) 
1473) DEFAULT_NOTES_TEMPLATE = """\
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1474) # Enter notes below the line with the cut mark (ASCII scissors and
1475) # dashes).  Lines above the cut mark (such as this one) will be ignored.
1476) #
1477) # If you wish to clear the notes, leave everything beyond the cut mark
1478) # blank.  However, if you leave the *entire* file blank, also removing
1479) # the cut mark, then the edit is aborted, and the old notes contents are
1480) # retained.
1481) #
1482) # - - - - - >8 - - - - - >8 - - - - - >8 - - - - - >8 - - - - -
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1483) """
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1484) DEFAULT_NOTES_MARKER = '# - - - - - >8 - - - - -'
1485) 
1486) 
Marco Ricci Reimplement deprecated subc...

Marco Ricci authored 1 month ago

1487) @derivepassphrase.command(
1488)     'vault',
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1489)     context_settings={'help_option_names': ['-h', '--help']},
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1490)     cls=CommandWithHelpGroups,
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1491)     epilog=r"""
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1492)         WARNING: There is NO WAY to retrieve the generated passphrases
1493)         if the master passphrase, the SSH key, or the exact passphrase
1494)         settings are lost, short of trying out all possible
1495)         combinations.  You are STRONGLY advised to keep independent
1496)         backups of the settings and the SSH key, if any.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1497) 
1498)         The configuration is NOT encrypted, and you are STRONGLY
1499)         discouraged from using a stored passphrase.
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1500)     """,
1501) )
1502) @click.option(
1503)     '-p',
1504)     '--phrase',
1505)     'use_phrase',
1506)     is_flag=True,
1507)     help='prompts you for your passphrase',
1508)     cls=PasswordGenerationOption,
1509) )
1510) @click.option(
1511)     '-k',
1512)     '--key',
1513)     'use_key',
1514)     is_flag=True,
1515)     help='uses your SSH private key to generate passwords',
1516)     cls=PasswordGenerationOption,
1517) )
1518) @click.option(
1519)     '-l',
1520)     '--length',
1521)     metavar='NUMBER',
1522)     callback=_validate_length,
1523)     help='emits password of length NUMBER',
1524)     cls=PasswordGenerationOption,
1525) )
1526) @click.option(
1527)     '-r',
1528)     '--repeat',
1529)     metavar='NUMBER',
1530)     callback=_validate_occurrence_constraint,
1531)     help='allows maximum of NUMBER repeated adjacent chars',
1532)     cls=PasswordGenerationOption,
1533) )
1534) @click.option(
1535)     '--lower',
1536)     metavar='NUMBER',
1537)     callback=_validate_occurrence_constraint,
1538)     help='includes at least NUMBER lowercase letters',
1539)     cls=PasswordGenerationOption,
1540) )
1541) @click.option(
1542)     '--upper',
1543)     metavar='NUMBER',
1544)     callback=_validate_occurrence_constraint,
1545)     help='includes at least NUMBER uppercase letters',
1546)     cls=PasswordGenerationOption,
1547) )
1548) @click.option(
1549)     '--number',
1550)     metavar='NUMBER',
1551)     callback=_validate_occurrence_constraint,
1552)     help='includes at least NUMBER digits',
1553)     cls=PasswordGenerationOption,
1554) )
1555) @click.option(
1556)     '--space',
1557)     metavar='NUMBER',
1558)     callback=_validate_occurrence_constraint,
1559)     help='includes at least NUMBER spaces',
1560)     cls=PasswordGenerationOption,
1561) )
1562) @click.option(
1563)     '--dash',
1564)     metavar='NUMBER',
1565)     callback=_validate_occurrence_constraint,
1566)     help='includes at least NUMBER "-" or "_"',
1567)     cls=PasswordGenerationOption,
1568) )
1569) @click.option(
1570)     '--symbol',
1571)     metavar='NUMBER',
1572)     callback=_validate_occurrence_constraint,
1573)     help='includes at least NUMBER symbol chars',
1574)     cls=PasswordGenerationOption,
1575) )
1576) @click.option(
1577)     '-n',
1578)     '--notes',
1579)     'edit_notes',
1580)     is_flag=True,
1581)     help='spawn an editor to edit notes for SERVICE',
1582)     cls=ConfigurationOption,
1583) )
1584) @click.option(
1585)     '-c',
1586)     '--config',
1587)     'store_config_only',
1588)     is_flag=True,
1589)     help='saves the given settings for SERVICE or global',
1590)     cls=ConfigurationOption,
1591) )
1592) @click.option(
1593)     '-x',
1594)     '--delete',
1595)     'delete_service_settings',
1596)     is_flag=True,
1597)     help='deletes settings for SERVICE',
1598)     cls=ConfigurationOption,
1599) )
1600) @click.option(
1601)     '--delete-globals',
1602)     is_flag=True,
1603)     help='deletes the global shared settings',
1604)     cls=ConfigurationOption,
1605) )
1606) @click.option(
1607)     '-X',
1608)     '--clear',
1609)     'clear_all_settings',
1610)     is_flag=True,
1611)     help='deletes all settings',
1612)     cls=ConfigurationOption,
1613) )
1614) @click.option(
1615)     '-e',
1616)     '--export',
1617)     'export_settings',
1618)     metavar='PATH',
1619)     help='export all saved settings into file PATH',
1620)     cls=StorageManagementOption,
1621) )
1622) @click.option(
1623)     '-i',
1624)     '--import',
1625)     'import_settings',
1626)     metavar='PATH',
1627)     help='import saved settings from file PATH',
1628)     cls=StorageManagementOption,
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1629) )
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1630) @click.option(
1631)     '--overwrite-existing/--merge-existing',
1632)     'overwrite_config',
1633)     default=False,
1634)     help='overwrite or merge (default) the existing configuration',
1635)     cls=CompatibilityOption,
1636) )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1637) @click.version_option(version=dpp.__version__, prog_name=PROG_NAME)
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1638) @standard_logging_options
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1639) @click.argument('service', required=False)
1640) @click.pass_context
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

1641) def derivepassphrase_vault(  # noqa: C901,PLR0912,PLR0913,PLR0914,PLR0915
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1642)     ctx: click.Context,
1643)     /,
1644)     *,
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1645)     service: str | None = None,
1646)     use_phrase: bool = False,
1647)     use_key: bool = False,
1648)     length: int | None = None,
1649)     repeat: int | None = None,
1650)     lower: int | None = None,
1651)     upper: int | None = None,
1652)     number: int | None = None,
1653)     space: int | None = None,
1654)     dash: int | None = None,
1655)     symbol: int | None = None,
1656)     edit_notes: bool = False,
1657)     store_config_only: bool = False,
1658)     delete_service_settings: bool = False,
1659)     delete_globals: bool = False,
1660)     clear_all_settings: bool = False,
1661)     export_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,
1662)     import_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1663)     overwrite_config: bool = False,
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1664) ) -> None:
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

1665)     """Derive a passphrase using the vault(1) derivation scheme.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1666) 
Marco Ricci Fill out README and documen...

Marco Ricci authored 6 months ago

1667)     Using a master passphrase or a master SSH key, derive a passphrase
1668)     for SERVICE, subject to length, character and character repetition
1669)     constraints.  The derivation is cryptographically strong, meaning
1670)     that even if a single passphrase is compromised, guessing the master
1671)     passphrase or a different service's passphrase is computationally
1672)     infeasible.  The derivation is also deterministic, given the same
1673)     inputs, thus the resulting passphrase need not be stored explicitly.
1674)     The service name and constraints themselves also need not be kept
1675)     secret; the latter are usually stored in a world-readable file.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1676) 
1677)     If operating on global settings, or importing/exporting settings,
1678)     then SERVICE must be omitted.  Otherwise it is required.\f
1679) 
1680)     This is a [`click`][CLICK]-powered command-line interface function,
1681)     and not intended for programmatic use.  Call with arguments
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1682)     `['--help']` to see full documentation of the interface.  (See also
1683)     [`click.testing.CliRunner`][] for controlled, programmatic
1684)     invocation.)
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1685) 
Marco Ricci Update all URLs to stable a...

Marco Ricci authored 3 months ago

1686)     [CLICK]: https://pypi.org/package/click/
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1687) 
1688)     Parameters:
1689)         ctx (click.Context):
1690)             The `click` context.
1691) 
1692)     Other Parameters:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1693)         service:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1694)             A service name.  Required, unless operating on global
1695)             settings or importing/exporting settings.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1696)         use_phrase:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1697)             Command-line argument `-p`/`--phrase`.  If given, query the
1698)             user for a passphrase instead of an SSH key.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1699)         use_key:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1700)             Command-line argument `-k`/`--key`.  If given, query the
1701)             user for an SSH key instead of a passphrase.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1702)         length:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1703)             Command-line argument `-l`/`--length`.  Override the default
1704)             length of the generated passphrase.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1705)         repeat:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1706)             Command-line argument `-r`/`--repeat`.  Override the default
1707)             repetition limit if positive, or disable the repetition
1708)             limit if 0.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1709)         lower:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1710)             Command-line argument `--lower`.  Require a given amount of
1711)             ASCII lowercase characters if positive, else forbid ASCII
1712)             lowercase characters if 0.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1713)         upper:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1714)             Command-line argument `--upper`.  Same as `lower`, but for
1715)             ASCII uppercase characters.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1716)         number:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1717)             Command-line argument `--number`.  Same as `lower`, but for
1718)             ASCII digits.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1719)         space:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1720)             Command-line argument `--space`.  Same as `lower`, but for
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1721)             the space character.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1722)         dash:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1723)             Command-line argument `--dash`.  Same as `lower`, but for
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1724)             the hyphen-minus and underscore characters.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1725)         symbol:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1726)             Command-line argument `--symbol`.  Same as `lower`, but for
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1727)             all other ASCII printable characters (except backquote).
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1728)         edit_notes:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1729)             Command-line argument `-n`/`--notes`.  If given, spawn an
1730)             editor to edit notes for `service`.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1731)         store_config_only:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1732)             Command-line argument `-c`/`--config`.  If given, saves the
1733)             other given settings (`--key`, ..., `--symbol`) to the
1734)             configuration file, either specifically for `service` or as
1735)             global settings.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1736)         delete_service_settings:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1737)             Command-line argument `-x`/`--delete`.  If given, removes
1738)             the settings for `service` from the configuration file.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1739)         delete_globals:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1740)             Command-line argument `--delete-globals`.  If given, removes
1741)             the global settings from the configuration file.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1742)         clear_all_settings:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1743)             Command-line argument `-X`/`--clear`.  If given, removes all
1744)             settings from the configuration file.
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1745)         export_settings:
1746)             Command-line argument `-e`/`--export`.  If a file object,
1747)             then it must be open for writing and accept `str` inputs.
1748)             Otherwise, a filename to open for writing.  Using `-` for
1749)             standard output is supported.
1750)         import_settings:
1751)             Command-line argument `-i`/`--import`.  If a file object, it
1752)             must be open for reading and yield `str` values.  Otherwise,
1753)             a filename to open for reading.  Using `-` for standard
1754)             input is supported.
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1755)         overwrite_config:
1756)             Command-line arguments `--overwrite-existing` (True) and
1757)             `--merge-existing` (False).  Controls whether config saving
1758)             and config importing overwrite existing configurations, or
1759)             merge them section-wise instead.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1760) 
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1761)     """  # noqa: D301
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1762)     logger = logging.getLogger(PROG_NAME)
1763)     deprecation = logging.getLogger(PROG_NAME + '.deprecation')
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1764)     options_in_group: dict[type[click.Option], list[click.Option]] = {}
1765)     params_by_str: dict[str, click.Parameter] = {}
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1766)     for param in ctx.command.params:
1767)         if isinstance(param, click.Option):
1768)             group: type[click.Option]
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

1769)             # Use match/case here once Python 3.9 becomes unsupported.
1770)             if isinstance(param, PasswordGenerationOption):
1771)                 group = PasswordGenerationOption
1772)             elif isinstance(param, ConfigurationOption):
1773)                 group = ConfigurationOption
1774)             elif isinstance(param, StorageManagementOption):
1775)                 group = StorageManagementOption
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1776)             elif isinstance(param, LoggingOption):
1777)                 group = LoggingOption
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1778)             elif isinstance(param, CompatibilityOption):
1779)                 group = CompatibilityOption
Marco Ricci Add support for Python 3.9

Marco Ricci authored 3 months ago

1780)             elif isinstance(param, OptionGroupOption):
1781)                 raise AssertionError(  # noqa: DOC501,TRY003,TRY004
1782)                     f'Unknown option group for {param!r}'  # noqa: EM102
1783)                 )
1784)             else:
1785)                 group = click.Option
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1786)             options_in_group.setdefault(group, []).append(param)
1787)         params_by_str[param.human_readable_name] = param
1788)         for name in param.opts + param.secondary_opts:
1789)             params_by_str[name] = param
1790) 
Marco Ricci Fix typing issues in mypy s...

Marco Ricci authored 5 months ago

1791)     def is_param_set(param: click.Parameter) -> bool:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1792)         return bool(ctx.params.get(param.human_readable_name))
1793) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1794)     def check_incompatible_options(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1795)         param: click.Parameter | str,
1796)         *incompatible: click.Parameter | str,
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1797)     ) -> None:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1798)         if isinstance(param, str):
1799)             param = params_by_str[param]
1800)         assert isinstance(param, click.Parameter)
1801)         if not is_param_set(param):
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1802)             return
1803)         for other in incompatible:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1804)             if isinstance(other, str):
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1805)                 other = params_by_str[other]  # noqa: PLW2901
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1806)             assert isinstance(other, click.Parameter)
1807)             if other != param and is_param_set(other):
1808)                 opt_str = param.opts[0]
1809)                 other_str = other.opts[0]
1810)                 raise click.BadOptionUsage(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1811)                     opt_str, f'mutually exclusive with {other_str}', ctx=ctx
1812)                 )
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1813) 
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1814)     def err(msg: Any, *args: Any, **kwargs: Any) -> NoReturn:  # noqa: ANN401
1815)         stacklevel = kwargs.pop('stacklevel', 1)
1816)         stacklevel += 1
1817)         logger.error(msg, *args, stacklevel=stacklevel, **kwargs)
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1818)         ctx.exit(1)
1819) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

1820)     def get_config() -> _types.VaultConfig:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1821)         try:
1822)             return _load_config()
1823)         except FileNotFoundError:
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1824)             try:
1825)                 backup_config, exc = _migrate_and_load_old_config()
1826)             except FileNotFoundError:
1827)                 return {'services': {}}
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

1828)             old_name = os.path.basename(
1829)                 _config_filename(subsystem='old settings.json')
1830)             )
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1831)             new_name = os.path.basename(_config_filename(subsystem='vault'))
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1832)             deprecation.warning(
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1833)                 (
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1834)                     'Using deprecated v0.1-style config file %r, '
1835)                     'instead of v0.2-style %r.  '
1836)                     'Support for v0.1-style config filenames will be '
1837)                     'removed in v1.0.'
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1838)                 ),
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1839)                 old_name,
1840)                 new_name,
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1841)             )
1842)             if isinstance(exc, OSError):
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1843)                 logger.warning(
1844)                     'Failed to migrate to %r: %s: %r',
1845)                     new_name,
1846)                     exc.strerror,
1847)                     exc.filename,
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1848)                 )
1849)             else:
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

1850)                 deprecation.info('Successfully migrated to %r.', new_name)
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1851)             return backup_config
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1852)         except OSError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1853)             err('Cannot load config: %s: %r', e.strerror, e.filename)
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1854)         except Exception as e:  # noqa: BLE001
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1855)             err('Cannot load config: %s', str(e), exc_info=e)
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1856) 
1857)     def put_config(config: _types.VaultConfig, /) -> None:
1858)         try:
1859)             _save_config(config)
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

1860)         except OSError as exc:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1861)             err('Cannot store config: %s: %r', exc.strerror, exc.filename)
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1862)         except Exception as exc:  # noqa: BLE001
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1863)             err('Cannot store config: %s', str(exc), exc_info=exc)
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1864) 
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

1865)     def get_user_config() -> dict[str, Any]:
1866)         try:
1867)             return _load_user_config()
1868)         except FileNotFoundError:
1869)             return {}
1870)         except OSError as e:
1871)             err('Cannot load user config: %s: %r', e.strerror, e.filename)
1872)         except Exception as e:  # noqa: BLE001
1873)             err('Cannot load user config: %s', str(e), exc_info=e)
1874) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

1875)     configuration: _types.VaultConfig
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1876) 
1877)     check_incompatible_options('--phrase', '--key')
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1878)     for group in (ConfigurationOption, StorageManagementOption):
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1879)         for opt in options_in_group[group]:
1880)             if opt != params_by_str['--config']:
1881)                 check_incompatible_options(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1882)                     opt, *options_in_group[PasswordGenerationOption]
1883)                 )
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1884) 
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1885)     for group in (ConfigurationOption, StorageManagementOption):
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1886)         for opt in options_in_group[group]:
1887)             check_incompatible_options(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1888)                 opt,
1889)                 *options_in_group[ConfigurationOption],
1890)                 *options_in_group[StorageManagementOption],
1891)             )
Marco Ricci Correctly model vault globa...

Marco Ricci authored 2 months ago

1892)     sv_or_global_options = options_in_group[PasswordGenerationOption]
1893)     for param in sv_or_global_options:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1894)         if is_param_set(param) and not (
1895)             service or is_param_set(params_by_str['--config'])
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1896)         ):
1897)             opt_str = param.opts[0]
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1898)             msg = f'{opt_str} requires a SERVICE or --config'
Marco Ricci Correctly model vault globa...

Marco Ricci authored 2 months ago

1899)             raise click.UsageError(msg)  # noqa: DOC501
1900)     sv_options = [params_by_str['--notes'], params_by_str['--delete']]
1901)     for param in sv_options:
1902)         if is_param_set(param) and not service:
1903)             opt_str = param.opts[0]
1904)             msg = f'{opt_str} requires a SERVICE'
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1905)             raise click.UsageError(msg)
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1906)     no_sv_options = [
1907)         params_by_str['--delete-globals'],
1908)         params_by_str['--clear'],
1909)         *options_in_group[StorageManagementOption],
1910)     ]
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1911)     for param in no_sv_options:
1912)         if is_param_set(param) and service:
1913)             opt_str = param.opts[0]
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

1914)             msg = f'{opt_str} does not take a SERVICE argument'
1915)             raise click.UsageError(msg)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1916) 
Marco Ricci Introduce a central user co...

Marco Ricci authored 3 weeks ago

1917)     user_config = get_user_config()
1918) 
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

1919)     if service == '':  # noqa: PLC1901
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1920)         logger.warning(
1921)             'An empty SERVICE is not supported by vault(1).  '
1922)             'For compatibility, this will be treated as if SERVICE '
1923)             'was not supplied, i.e., it will error out, or '
1924)             'operate on global settings.'
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

1925)         )
1926) 
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1927)     if edit_notes:
1928)         assert service is not None
1929)         configuration = get_config()
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1930)         text = DEFAULT_NOTES_TEMPLATE + configuration['services'].get(
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

1931)             service, cast(_types.VaultConfigServicesSettings, {})
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1932)         ).get('notes', '')
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1933)         notes_value = click.edit(text=text)
1934)         if notes_value is not None:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1935)             notes_lines = collections.deque(notes_value.splitlines(True))  # noqa: FBT003
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1936)             while notes_lines:
1937)                 line = notes_lines.popleft()
1938)                 if line.startswith(DEFAULT_NOTES_MARKER):
1939)                     notes_value = ''.join(notes_lines)
1940)                     break
1941)             else:
1942)                 if not notes_value.strip():
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

1943)                     err('Not saving new notes: user aborted request')
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1944)             configuration['services'].setdefault(service, {})['notes'] = (
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1945)                 notes_value.strip('\n')
1946)             )
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1947)             put_config(configuration)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1948)     elif delete_service_settings:
1949)         assert service is not None
1950)         configuration = get_config()
1951)         if service in configuration['services']:
1952)             del configuration['services'][service]
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1953)             put_config(configuration)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1954)     elif delete_globals:
1955)         configuration = get_config()
1956)         if 'global' in configuration:
1957)             del configuration['global']
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1958)             put_config(configuration)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1959)     elif clear_all_settings:
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

1960)         put_config({'services': {}})
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1961)     elif import_settings:
1962)         try:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1963)             # TODO(the-13th-letter): keep track of auto-close; try
1964)             # os.dup if feasible
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1965)             infile = (
1966)                 cast(TextIO, import_settings)
1967)                 if hasattr(import_settings, 'close')
1968)                 else click.open_file(os.fspath(import_settings), 'rt')
1969)             )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1970)             with infile:
1971)                 maybe_config = json.load(infile)
1972)         except json.JSONDecodeError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1973)             err('Cannot load config: cannot decode JSON: %s', e)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1974)         except OSError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1975)             err('Cannot load config: %s: %r', e.strerror, e.filename)
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1976)         cleaned = _types.clean_up_falsy_vault_config_values(maybe_config)
1977)         if not _types.is_vault_config(maybe_config):
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1978)             err('Cannot load config: %s', _INVALID_VAULT_CONFIG)
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1979)         assert cleaned is not None
1980)         for step in cleaned:
1981)             # These are never fatal errors, because the semantics of
1982)             # vault upon encountering these settings are ill-specified,
1983)             # but not ill-defined.
1984)             if step.action == 'replace':
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1985)                 logger.warning(
1986)                     'Replacing invalid value %s for key %s with %s.',
1987)                     json.dumps(step.old_value),
1988)                     _types.json_path(step.path),
1989)                     json.dumps(step.new_value),
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1990)                 )
1991)             else:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1992)                 logger.warning(
1993)                     'Removing ineffective setting %s = %s.',
1994)                     _types.json_path(step.path),
1995)                     json.dumps(step.old_value),
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

1996)                 )
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

1997)         if '' in maybe_config['services']:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

1998)             logger.warning(
1999)                 (
2000)                     'An empty SERVICE is not supported by vault(1), '
2001)                     'and the empty-string service settings will be '
2002)                     'inaccessible and ineffective.  To ensure that '
2003)                     'vault(1) and %s see the settings, move them '
2004)                     'into the "global" section.'
2005)                 ),
2006)                 PROG_NAME,
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

2007)             )
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2008)         try:
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

2009)             _check_for_misleading_passphrase(
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2010)                 ('global',),
2011)                 cast(dict[str, Any], maybe_config.get('global', {})),
2012)                 main_config=user_config,
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

2013)             )
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2014)             for key, value in maybe_config['services'].items():
2015)                 _check_for_misleading_passphrase(
2016)                     ('services', key),
2017)                     cast(dict[str, Any], value),
2018)                     main_config=user_config,
2019)                 )
2020)         except AssertionError as e:
2021)             err('The configuration file is invalid.  ' + str(e))
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

2022)         if overwrite_config:
2023)             put_config(maybe_config)
2024)         else:
2025)             configuration = get_config()
2026)             merged_config: collections.ChainMap[str, Any] = (
2027)                 collections.ChainMap(
2028)                     {
2029)                         'services': collections.ChainMap(
2030)                             maybe_config['services'],
2031)                             configuration['services'],
2032)                         ),
2033)                     },
2034)                     {'global': maybe_config['global']}
2035)                     if 'global' in maybe_config
2036)                     else {},
2037)                     {'global': configuration['global']}
2038)                     if 'global' in configuration
2039)                     else {},
2040)                 )
2041)             )
2042)             new_config: Any = {
2043)                 k: dict(v) if isinstance(v, collections.ChainMap) else v
2044)                 for k, v in sorted(merged_config.items())
2045)             }
2046)             assert _types.is_vault_config(new_config)
2047)             put_config(new_config)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2048)     elif export_settings:
2049)         configuration = get_config()
2050)         try:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

2051)             # TODO(the-13th-letter): keep track of auto-close; try
2052)             # os.dup if feasible
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2053)             outfile = (
2054)                 cast(TextIO, export_settings)
2055)                 if hasattr(export_settings, 'close')
2056)                 else click.open_file(os.fspath(export_settings), 'wt')
2057)             )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2058)             with outfile:
2059)                 json.dump(configuration, outfile)
2060)         except OSError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2061)             err('Cannot store config: %s: %r', e.strerror, e.filename)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2062)     else:
2063)         configuration = get_config()
2064)         # This block could be type checked more stringently, but this
2065)         # would probably involve a lot of code repetition.  Since we
2066)         # have a type guarding function anyway, assert that we didn't
2067)         # make any mistakes at the end instead.
2068)         global_keys = {'key', 'phrase'}
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2069)         service_keys = {
2070)             'key',
2071)             'phrase',
2072)             'length',
2073)             'repeat',
2074)             'lower',
2075)             'upper',
2076)             'number',
2077)             'space',
2078)             'dash',
2079)             'symbol',
2080)         }
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2081)         settings: collections.ChainMap[str, Any] = collections.ChainMap(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2082)             {
2083)                 k: v
2084)                 for k, v in locals().items()
2085)                 if k in service_keys and v is not None
2086)             },
2087)             cast(
2088)                 dict[str, Any],
2089)                 configuration['services'].get(service or '', {}),
2090)             ),
2091)             cast(dict[str, Any], configuration.get('global', {})),
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2092)         )
2093)         if use_key:
2094)             try:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2095)                 key = base64.standard_b64encode(_select_ssh_key()).decode(
2096)                     'ASCII'
2097)                 )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2098)             except IndexError:
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2099)                 err('No valid SSH key selected')
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

2100)             except KeyError:
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2101)                 err('Cannot find running SSH agent; check SSH_AUTH_SOCK')
Marco Ricci Fail gracefully if UNIX dom...

Marco Ricci authored 3 months ago

2102)             except NotImplementedError:
2103)                 err(
2104)                     'Cannot connect to SSH agent because '
2105)                     'this Python version does not support UNIX domain sockets'
2106)                 )
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

2107)             except OSError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2108)                 err('Cannot connect to SSH agent: %s', e.strerror)
Marco Ricci Add a specific error class...

Marco Ricci authored 4 months ago

2109)             except (
2110)                 LookupError,
2111)                 RuntimeError,
2112)                 ssh_agent.SSHAgentFailedError,
2113)             ) as e:
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

2114)                 err(str(e))
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2115)         elif use_phrase:
2116)             maybe_phrase = _prompt_for_passphrase()
2117)             if not maybe_phrase:
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2118)                 err('No passphrase given')
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2119)             else:
2120)                 phrase = maybe_phrase
2121)         if store_config_only:
2122)             view: collections.ChainMap[str, Any]
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2123)             view = (
2124)                 collections.ChainMap(*settings.maps[:2])
2125)                 if service
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

2126)                 else collections.ChainMap(settings.maps[0], settings.maps[2])
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2127)             )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2128)             if use_key:
2129)                 view['key'] = key
2130)             elif use_phrase:
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

2131)                 view['phrase'] = phrase
2132)                 settings_type = 'service' if service else 'global'
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2133)                 try:
2134)                     _check_for_misleading_passphrase(
2135)                         ('services', service) if service else ('global',),
2136)                         {'phrase': phrase},
2137)                         main_config=user_config,
2138)                     )
2139)                 except AssertionError as e:
2140)                     err('The configuration file is invalid.  ' + str(e))
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

2141)                 if 'key' in settings:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2142)                     logger.warning(
2143)                         (
2144)                             'Setting a %s passphrase is ineffective '
2145)                             'because a key is also set.'
2146)                         ),
2147)                         settings_type,
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

2148)                     )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2149)             if not view.maps[0]:
2150)                 settings_type = 'service' if service else 'global'
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2151)                 msg = (
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2152)                     f'Cannot update {settings_type} settings without '
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2153)                     f'actual settings'
2154)                 )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2155)                 raise click.UsageError(msg)
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

2156)             subtree: dict[str, Any] = (
2157)                 configuration['services'].setdefault(service, {})  # type: ignore[assignment]
2158)                 if service
2159)                 else configuration.setdefault('global', {})
2160)             )
2161)             if overwrite_config:
2162)                 subtree.clear()
2163)             subtree.update(view)
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

2164)             assert _types.is_vault_config(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2165)                 configuration
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2166)             ), f'Invalid vault configuration: {configuration!r}'
Marco Ricci Fix error bubbling in outda...

Marco Ricci authored 4 months ago

2167)             put_config(configuration)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2168)         else:
2169)             if not service:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2170)                 msg = 'SERVICE is required'
2171)                 raise click.UsageError(msg)
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2172)             kwargs: dict[str, Any] = {
2173)                 k: v
2174)                 for k, v in settings.items()
2175)                 if k in service_keys and v is not None
2176)             }
2177) 
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

2178)             if use_phrase:
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2179)                 try:
2180)                     _check_for_misleading_passphrase(
2181)                         _ORIGIN.INTERACTIVE,
2182)                         {'phrase': phrase},
2183)                         main_config=user_config,
2184)                     )
2185)                 except AssertionError as e:
2186)                     err('The configuration file is invalid.  ' + str(e))
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

2187) 
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2188)             # If either --key or --phrase are given, use that setting.
2189)             # Otherwise, if both key and phrase are set in the config,
Marco Ricci Align behavior with vault c...

Marco Ricci authored 3 months ago

2190)             # use the key.  Otherwise, if only one of key and phrase is
2191)             # set in the config, use that one.  In all these above
2192)             # cases, set the phrase via vault.Vault.phrase_from_key if
2193)             # a key is given.  Finally, if nothing is set, error out.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2194)             if use_key or use_phrase:
Marco Ricci Hoist and add tests for int...

Marco Ricci authored 3 weeks ago

2195)                 kwargs['phrase'] = _key_to_phrase(
2196)                     key, error_callback=err
2197)                 ) if use_key else phrase
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2198)             elif kwargs.get('key'):
Marco Ricci Hoist and add tests for int...

Marco Ricci authored 3 weeks ago

2199)                 kwargs['phrase'] = _key_to_phrase(
2200)                     kwargs['key'], error_callback=err
2201)                 )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2202)             elif kwargs.get('phrase'):
2203)                 pass
2204)             else:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2205)                 msg = (
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

2206)                     'No passphrase or key given on command-line '
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2207)                     'or in configuration'
2208)                 )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2209)                 raise click.UsageError(msg)
Marco Ricci Avoid crashing when overrid...

Marco Ricci authored 5 months ago

2210)             kwargs.pop('key', '')
Marco Ricci Move `sequin` and `ssh_agen...

Marco Ricci authored 5 months ago

2211)             result = vault.Vault(**kwargs).generate(service)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2212)             click.echo(result.decode('ASCII'))