7a43a0cf4ea5877b3ff07576d5ee5c3ad36c087b
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 Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

15) import functools
Marco Ricci Reintegrate all functionali...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

897) 
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

997) 
Marco Ricci Generate nicer documentatio...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

1154)     try:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

1315)                 (
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 weeks ago

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

Marco Ricci authored 6 months ago

1400) def _validate_occurrence_constraint(
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 weeks ago

1631) @click.option(
1632)     '--overwrite-existing/--merge-existing',
1633)     'overwrite_config',
1634)     default=False,
1635)     help='overwrite or merge (default) the existing configuration',
1636)     cls=CompatibilityOption,
1637) )
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

1638) @click.option(
1639)     '--unset',
1640)     'unset_settings',
1641)     multiple=True,
1642)     type=click.Choice([
1643)         'phrase',
1644)         'key',
1645)         'length',
1646)         'repeat',
1647)         'lower',
1648)         'upper',
1649)         'number',
1650)         'space',
1651)         'dash',
1652)         'symbol',
1653)     ]),
1654)     help=(
1655)         'with --config, also unsets the given setting; '
1656)         'may be specified multiple times'
1657)     ),
1658)     cls=CompatibilityOption,
1659) )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 5 months ago

1665)     ctx: click.Context,
1666)     /,
1667)     *,
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1668)     service: str | None = None,
1669)     use_phrase: bool = False,
1670)     use_key: bool = False,
1671)     length: int | None = None,
1672)     repeat: int | None = None,
1673)     lower: int | None = None,
1674)     upper: int | None = None,
1675)     number: int | None = None,
1676)     space: int | None = None,
1677)     dash: int | None = None,
1678)     symbol: int | None = None,
1679)     edit_notes: bool = False,
1680)     store_config_only: bool = False,
1681)     delete_service_settings: bool = False,
1682)     delete_globals: bool = False,
1683)     clear_all_settings: bool = False,
1684)     export_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,
1685)     import_settings: TextIO | pathlib.Path | os.PathLike[str] | None = None,
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1686)     overwrite_config: bool = False,
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

1687)     unset_settings: Sequence[str] = (),
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

1691)     Using a master passphrase or a master SSH key, derive a passphrase
1692)     for SERVICE, subject to length, character and character repetition
1693)     constraints.  The derivation is cryptographically strong, meaning
1694)     that even if a single passphrase is compromised, guessing the master
1695)     passphrase or a different service's passphrase is computationally
1696)     infeasible.  The derivation is also deterministic, given the same
1697)     inputs, thus the resulting passphrase need not be stored explicitly.
1698)     The service name and constraints themselves also need not be kept
1699)     secret; the latter are usually stored in a world-readable file.
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

1711) 
1712)     Parameters:
1713)         ctx (click.Context):
1714)             The `click` context.
1715) 
1716)     Other Parameters:
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

1769)         export_settings:
1770)             Command-line argument `-e`/`--export`.  If a file object,
1771)             then it must be open for writing and accept `str` inputs.
1772)             Otherwise, a filename to open for writing.  Using `-` for
1773)             standard output is supported.
1774)         import_settings:
1775)             Command-line argument `-i`/`--import`.  If a file object, it
1776)             must be open for reading and yield `str` values.  Otherwise,
1777)             a filename to open for reading.  Using `-` for standard
1778)             input is supported.
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

1779)         overwrite_config:
1780)             Command-line arguments `--overwrite-existing` (True) and
1781)             `--merge-existing` (False).  Controls whether config saving
1782)             and config importing overwrite existing configurations, or
1783)             merge them section-wise instead.
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

1784)         unset_settings:
1785)             Command-line argument `--unset`.  If given together with
1786)             `--config`, unsets the specified settings (in addition to
1787)             any other changes requested).
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

1797)             # Use match/case here once Python 3.9 becomes unsupported.
1798)             if isinstance(param, PasswordGenerationOption):
1799)                 group = PasswordGenerationOption
1800)             elif isinstance(param, ConfigurationOption):
1801)                 group = ConfigurationOption
1802)             elif isinstance(param, StorageManagementOption):
1803)                 group = StorageManagementOption
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 weeks ago

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

Marco Ricci authored 3 months ago

1808)             elif isinstance(param, OptionGroupOption):
1809)                 raise AssertionError(  # noqa: DOC501,TRY003,TRY004
1810)                     f'Unknown option group for {param!r}'  # noqa: EM102
1811)                 )
1812)             else:
1813)                 group = click.Option
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

1814)             options_in_group.setdefault(group, []).append(param)
1815)         params_by_str[param.human_readable_name] = param
1816)         for name in param.opts + param.secondary_opts:
1817)             params_by_str[name] = param
1818) 
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1819)     @functools.cache
Marco Ricci Fix typing issues in mypy s...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

1823)     def check_incompatible_options(
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1824)         param1: click.Parameter | str,
1825)         param2: click.Parameter | str,
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1826)     ) -> None:
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1827)         param1 = params_by_str[param1] if isinstance(param1, str) else param1
1828)         param2 = params_by_str[param2] if isinstance(param2, str) else param2
1829)         if param1 == param2:
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

1830)             return
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1831)         if not is_param_set(param1):
1832)             return
1833)         if is_param_set(param2):
1834)             param1_str = param1.human_readable_name
1835)             param2_str = param2.human_readable_name
1836)             raise click.BadOptionUsage(
1837)                 param1_str, f'mutually exclusive with {param2_str}', ctx=ctx
1838)             )
1839)         return
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

1845)         ctx.exit(1)
1846) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

1848)         try:
1849)             return _load_config()
1850)         except FileNotFoundError:
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1851)             try:
1852)                 backup_config, exc = _migrate_and_load_old_config()
1853)             except FileNotFoundError:
1854)                 return {'services': {}}
Marco Ricci Make obtaining the compatib...

Marco Ricci authored 3 weeks ago

1855)             old_name = os.path.basename(
1856)                 _config_filename(subsystem='old settings.json')
1857)             )
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

1866)                 old_name,
1867)                 new_name,
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

1870)                 logger.warning(
1871)                     'Failed to migrate to %r: %s: %r',
1872)                     new_name,
1873)                     exc.strerror,
1874)                     exc.filename,
Marco Ricci Rename the configuration fi...

Marco Ricci authored 3 months ago

1875)                 )
1876)             else:
Marco Ricci Fix usage of `--debug`, `--...

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

1883) 
1884)     def put_config(config: _types.VaultConfig, /) -> None:
1885)         try:
1886)             _save_config(config)
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

1892)     def get_user_config() -> dict[str, Any]:
1893)         try:
1894)             return _load_user_config()
1895)         except FileNotFoundError:
1896)             return {}
1897)         except OSError as e:
1898)             err('Cannot load user config: %s: %r', e.strerror, e.filename)
1899)         except Exception as e:  # noqa: BLE001
1900)             err('Cannot load user config: %s', str(e), exc_info=e)
1901) 
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

1903) 
1904)     check_incompatible_options('--phrase', '--key')
Marco Ricci Add prototype command-line...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

1906)         for opt in options_in_group[group]:
1907)             if opt != params_by_str['--config']:
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1908)                 for other_opt in options_in_group[PasswordGenerationOption]:
1909)                     check_incompatible_options(opt, other_opt)
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 6 months ago

1912)         for opt in options_in_group[group]:
Marco Ricci Rewrite incompatible option...

Marco Ricci authored 3 weeks ago

1913)             for other_opt in options_in_group[ConfigurationOption]:
1914)                 check_incompatible_options(opt, other_opt)
1915)             for other_opt in options_in_group[StorageManagementOption]:
1916)                 check_incompatible_options(opt, other_opt)
Marco Ricci Correctly model vault globa...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 2 months ago

1924)             raise click.UsageError(msg)  # noqa: DOC501
1925)     sv_options = [params_by_str['--notes'], params_by_str['--delete']]
1926)     for param in sv_options:
1927)         if is_param_set(param) and not service:
1928)             opt_str = param.opts[0]
1929)             msg = f'{opt_str} requires a SERVICE'
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

1931)     no_sv_options = [
1932)         params_by_str['--delete-globals'],
1933)         params_by_str['--clear'],
1934)         *options_in_group[StorageManagementOption],
1935)     ]
Marco Ricci Fortify the argument parsin...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

1942)     user_config = get_user_config()
1943) 
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 2 months ago

1950)         )
1951) 
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

1952)     if edit_notes:
1953)         assert service is not None
1954)         configuration = get_config()
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

1961)             while notes_lines:
1962)                 line = notes_lines.popleft()
1963)                 if line.startswith(DEFAULT_NOTES_MARKER):
1964)                     notes_value = ''.join(notes_lines)
1965)                     break
1966)             else:
1967)                 if not notes_value.strip():
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

1970)                 notes_value.strip('\n')
1971)             )
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

1973)     elif delete_service_settings:
1974)         assert service is not None
1975)         configuration = get_config()
1976)         if service in configuration['services']:
1977)             del configuration['services'][service]
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

1979)     elif delete_globals:
1980)         configuration = get_config()
1981)         if 'global' in configuration:
1982)             del configuration['global']
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

1986)     elif import_settings:
1987)         try:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

1988)             # TODO(the-13th-letter): keep track of auto-close; try
1989)             # os.dup if feasible
Marco Ricci Document handling of file o...

Marco Ricci authored 3 weeks ago

1990)             infile = cast(
1991)                 TextIO,
1992)                 (
1993)                     import_settings
1994)                     if hasattr(import_settings, 'close')
1995)                     else click.open_file(os.fspath(import_settings), 'rt')
1996)                 ),
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

1997)             )
Marco Ricci Document handling of file o...

Marco Ricci authored 3 weeks ago

1998)             # Don't specifically catch TypeError or ValueError here if
1999)             # the passed-in fileobj is not a readable text stream.  This
2000)             # will never happen on the command-line (thanks to `click`),
2001)             # and for programmatic use, our caller may want accurate
2002)             # error information.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2003)             with infile:
2004)                 maybe_config = json.load(infile)
2005)         except json.JSONDecodeError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 3 months ago

2012)         assert cleaned is not None
2013)         for step in cleaned:
2014)             # These are never fatal errors, because the semantics of
2015)             # vault upon encountering these settings are ill-specified,
2016)             # but not ill-defined.
2017)             if step.action == 'replace':
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2018)                 logger.warning(
2019)                     'Replacing invalid value %s for key %s with %s.',
2020)                     json.dumps(step.old_value),
2021)                     _types.json_path(step.path),
2022)                     json.dumps(step.new_value),
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

2023)                 )
2024)             else:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2025)                 logger.warning(
2026)                     'Removing ineffective setting %s = %s.',
2027)                     _types.json_path(step.path),
2028)                     json.dumps(step.old_value),
Marco Ricci Signal and list falsy value...

Marco Ricci authored 3 months ago

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

Marco Ricci authored 2 months ago

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

Marco Ricci authored 1 month ago

2031)             logger.warning(
2032)                 (
2033)                     'An empty SERVICE is not supported by vault(1), '
2034)                     'and the empty-string service settings will be '
2035)                     'inaccessible and ineffective.  To ensure that '
2036)                     'vault(1) and %s see the settings, move them '
2037)                     'into the "global" section.'
2038)                 ),
2039)                 PROG_NAME,
Marco Ricci Warn the user upon supplyin...

Marco Ricci authored 2 months ago

2040)             )
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 4 months ago

2046)             )
Marco Ricci Turn Unicode normalization...

Marco Ricci authored 3 weeks ago

2047)             for key, value in maybe_config['services'].items():
2048)                 _check_for_misleading_passphrase(
2049)                     ('services', key),
2050)                     cast(dict[str, Any], value),
2051)                     main_config=user_config,
2052)                 )
2053)         except AssertionError as e:
2054)             err('The configuration file is invalid.  ' + str(e))
Marco Ricci Fix empty key handling in `...

Marco Ricci authored 3 weeks ago

2055)         global_obj = maybe_config.get('global', {})
2056)         has_key = _types.js_truthiness(global_obj.get('key'))
2057)         has_phrase = _types.js_truthiness(global_obj.get('phrase'))
2058)         if has_key and has_phrase:
2059)             logger.warning(
2060)                 'Setting a global passphrase is ineffective '
2061)                 'because a key is also set.'
2062)             )
2063)         for service_name, service_obj in maybe_config['services'].items():
2064)             has_key = _types.js_truthiness(
2065)                 service_obj.get('key')
2066)             ) or _types.js_truthiness(global_obj.get('key'))
2067)             has_phrase = _types.js_truthiness(
2068)                 service_obj.get('phrase')
2069)             ) or _types.js_truthiness(global_obj.get('phrase'))
2070)             if has_key and has_phrase:
2071)                 logger.warning(
2072)                     (
2073)                         'Setting a service passphrase is ineffective '
2074)                         'because a key is also set: %s'
2075)                     ),
2076)                     json.dumps(service_name),
2077)                 )
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

2078)         if overwrite_config:
2079)             put_config(maybe_config)
2080)         else:
2081)             configuration = get_config()
2082)             merged_config: collections.ChainMap[str, Any] = (
2083)                 collections.ChainMap(
2084)                     {
2085)                         'services': collections.ChainMap(
2086)                             maybe_config['services'],
2087)                             configuration['services'],
2088)                         ),
2089)                     },
2090)                     {'global': maybe_config['global']}
2091)                     if 'global' in maybe_config
2092)                     else {},
2093)                     {'global': configuration['global']}
2094)                     if 'global' in configuration
2095)                     else {},
2096)                 )
2097)             )
2098)             new_config: Any = {
2099)                 k: dict(v) if isinstance(v, collections.ChainMap) else v
2100)                 for k, v in sorted(merged_config.items())
2101)             }
2102)             assert _types.is_vault_config(new_config)
2103)             put_config(new_config)
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2104)     elif export_settings:
2105)         configuration = get_config()
2106)         try:
Marco Ricci Apply new ruff ruleset to c...

Marco Ricci authored 4 months ago

2107)             # TODO(the-13th-letter): keep track of auto-close; try
2108)             # os.dup if feasible
Marco Ricci Document handling of file o...

Marco Ricci authored 3 weeks ago

2109)             outfile = cast(
2110)                 TextIO,
2111)                 (
2112)                     export_settings
2113)                     if hasattr(export_settings, 'close')
2114)                     else click.open_file(os.fspath(export_settings), 'wt')
2115)                 ),
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2116)             )
Marco Ricci Document handling of file o...

Marco Ricci authored 3 weeks ago

2117)             # Don't specifically catch TypeError or ValueError here if
2118)             # the passed-in fileobj is not a writable text stream.  This
2119)             # will never happen on the command-line (thanks to `click`),
2120)             # and for programmatic use, our caller may want accurate
2121)             # error information.
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2122)             with outfile:
2123)                 json.dump(configuration, outfile)
2124)         except OSError as e:
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

2133)         service_keys = {
2134)             'key',
2135)             'phrase',
2136)             'length',
2137)             'repeat',
2138)             'lower',
2139)             'upper',
2140)             'number',
2141)             'space',
2142)             'dash',
2143)             'symbol',
2144)         }
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 5 months ago

2146)             {
2147)                 k: v
2148)                 for k, v in locals().items()
2149)                 if k in service_keys and v is not None
2150)             },
2151)             cast(
2152)                 dict[str, Any],
2153)                 configuration['services'].get(service or '', {}),
2154)             ),
2155)             cast(dict[str, Any], configuration.get('global', {})),
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2156)         )
2157)         if use_key:
2158)             try:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2159)                 key = base64.standard_b64encode(_select_ssh_key()).decode(
2160)                     'ASCII'
2161)                 )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 months ago

2166)             except NotImplementedError:
2167)                 err(
2168)                     'Cannot connect to SSH agent because '
2169)                     'this Python version does not support UNIX domain sockets'
2170)                 )
Marco Ricci Document and handle other e...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 1 month ago

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

Marco Ricci authored 4 months ago

2173)             except (
2174)                 LookupError,
2175)                 RuntimeError,
2176)                 ssh_agent.SSHAgentFailedError,
2177)             ) as e:
Marco Ricci Use better error message ha...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

2179)         elif use_phrase:
2180)             maybe_phrase = _prompt_for_passphrase()
2181)             if not maybe_phrase:
Marco Ricci Fix error message capitaliz...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

2183)             else:
2184)                 phrase = maybe_phrase
2185)         if store_config_only:
2186)             view: collections.ChainMap[str, Any]
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

2187)             view = (
2188)                 collections.ChainMap(*settings.maps[:2])
2189)                 if service
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

2192)             if use_key:
2193)                 view['key'] = key
2194)             elif use_phrase:
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

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

Marco Ricci authored 3 weeks ago

2197)                 try:
2198)                     _check_for_misleading_passphrase(
2199)                         ('services', service) if service else ('global',),
2200)                         {'phrase': phrase},
2201)                         main_config=user_config,
2202)                     )
2203)                 except AssertionError as e:
2204)                     err('The configuration file is invalid.  ' + str(e))
Marco Ricci Fix missing consideration o...

Marco Ricci authored 2 months ago

2205)                 if 'key' in settings:
Marco Ricci Fix empty key handling in `...

Marco Ricci authored 3 weeks ago

2206)                     if service:
2207)                         logger.warning(
2208)                             (
2209)                                 'Setting a service passphrase is ineffective '
2210)                                 'because a key is also set: %s'
2211)                             ),
2212)                             json.dumps(service),
2213)                         )
2214)                     else:
2215)                         logger.warning(
2216)                             'Setting a global passphrase is ineffective '
Marco Ricci Use the logging system to e...

Marco Ricci authored 1 month ago

2217)                             'because a key is also set.'
Marco Ricci Fix empty key handling in `...

Marco Ricci authored 3 weeks ago

2218)                         )
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

2219)             if not view.maps[0] and not unset_settings:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2220)                 settings_type = 'service' if service else 'global'
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

2223)                     f'actual settings'
2224)                 )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

2225)                 raise click.UsageError(msg)
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

2226)             for setting in unset_settings:
2227)                 if setting in view.maps[0]:
2228)                     msg = (
2229)                         f'Attempted to unset and set --{setting} '
2230)                         f'at the same time.'
2231)                     )
2232)                     raise click.UsageError(msg)
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

2233)             subtree: dict[str, Any] = (
2234)                 configuration['services'].setdefault(service, {})  # type: ignore[assignment]
2235)                 if service
2236)                 else configuration.setdefault('global', {})
2237)             )
2238)             if overwrite_config:
2239)                 subtree.clear()
Marco Ricci Allow unsetting settings wh...

Marco Ricci authored 2 weeks ago

2240)             else:
2241)                 for setting in unset_settings:
2242)                     subtree.pop(setting, None)
Marco Ricci Allow the user to overwrite...

Marco Ricci authored 4 weeks ago

2243)             subtree.update(view)
Marco Ricci Consolidate `types` submodu...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

2248)         else:
2249)             if not service:
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

2252)             kwargs: dict[str, Any] = {
2253)                 k: v
2254)                 for k, v in settings.items()
2255)                 if k in service_keys and v is not None
2256)             }
2257) 
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 3 weeks ago

2259)                 try:
2260)                     _check_for_misleading_passphrase(
2261)                         _ORIGIN.INTERACTIVE,
2262)                         {'phrase': phrase},
2263)                         main_config=user_config,
2264)                     )
2265)                 except AssertionError as e:
2266)                     err('The configuration file is invalid.  ' + str(e))
Marco Ricci Allow all textual strings,...

Marco Ricci authored 4 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 months ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

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

Marco Ricci authored 6 months ago

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

Marco Ricci authored 3 weeks ago

2279)                 kwargs['phrase'] = _key_to_phrase(
2280)                     kwargs['key'], error_callback=err
2281)                 )
Marco Ricci Add finished command-line i...

Marco Ricci authored 6 months ago

2282)             elif kwargs.get('phrase'):
2283)                 pass
2284)             else:
Marco Ricci Reformat everything with ruff

Marco Ricci authored 5 months ago

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

Marco Ricci authored 4 months ago

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

Marco Ricci authored 5 months ago

2287)                     'or in configuration'
2288)                 )
Marco Ricci Fix style issues with ruff...

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 5 months ago

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

Marco Ricci authored 6 months ago

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