62 lines
2.0 KiB
Python
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)
|