Files
sshecret/src/sshecret/password_readers.py

62 lines
2.0 KiB
Python

"""Password reader classes.
This implements two interfaces to read passwords:
InputPasswordReader and EnvironmentPasswordReader.
"""
import re
import os
import sys
from typing import TextIO, override
import click
from .types import BasePasswordReader
from . import constants
RE_VARNAME = re.compile(r"^[a-zA-Z_]+[a-zA-Z0-9_]*$")
class InputPasswordReader(BasePasswordReader):
"""Read a password from stdin."""
@override
def get_password(self, identifier: str, repeated: bool = False) -> str:
"""Get password."""
if password := click.prompt(
f"Enter password for {identifier}", hide_input=True, type=str, confirmation_prompt=repeated
):
return str(password)
raise ValueError("No password received.")
class EnvironmentPasswordReader(BasePasswordReader):
"""Read a password from the environment.
The environemnt variable will be constructured based on the identifier and the prefix.
Final environemnt variable will be validated according to the regex `[a-zA-Z_]+[a-zA-Z0-9_]*`
"""
def _resolve_var_name(self, identifier: str) -> str:
"""Resolve variable name."""
identifier = identifier.replace("-", "_")
fields = [constants.VAR_PREFIX, identifier]
varname = "_".join(fields)
if not RE_VARNAME.fullmatch(varname):
raise ValueError(
f"Cannot generate encode password identifier in variable name. {varname} is not a valid identifier."
)
return varname
def get_password_from_env(self, identifier: str) -> str:
"""Get password from environment."""
varname = self._resolve_var_name(identifier)
if password := os.getenv(varname, None):
return password
raise ValueError(f"Error: No variable named {varname} resolved.")
@override
def get_password(self, identifier: str, repeated: bool = False) -> str:
"""Get password."""
return self.get_password_from_env(identifier)