ALPHANUMERIC = LETTERS + DIGITS DIGITS = "0123456789" DISTINGUISHABLE = exclude(ALPHANUMERIC, 'Il1O0') LOWERCASE = "abcdefghijklmnopqrstuvwxyz" SYMBOLS = exclude(PUNCTUATION, """'"`\\""") UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" SHIFTED = UPPERCASE + '~!@#$%^&*()_+{}|:"<>?' def get_setting(name, default=None, expand=True):
    name = name.lower()
    try:
        value = Config[name]
    except KeyError:
        try:
            value = CONFIG_DEFAULTS[name]
        except KeyError:
            try:
                value = NONCONFIG_SETTINGS[name]
            except KeyError:
                return default
    if value is None:
        return default
    if name == 'gpg_ids':
        value = value.split() if is_str(value) else value
    elif name.endswith('_dir'):
        value = Path(value)
    elif expand and name.endswith('_file'):
        value = get_setting('settings_dir') / value
    return value class PasswordError(Error):
    """Password error.

    This exception subclasses
    `Inform.Error <https://github.com/KenKundert/inform#exception>`_.
    """
    def __init__(self, *args, **kwargs):
        self.args = args
        self.kwargs = kwargs class Dictionary(object):
    """Read Dictionary"""
    def __init__(self, dictionary=None):
        self.dictionary = dictionary
        self.words = known_dictionaries.get(dictionary)

    def get_words(self):
        if not self.words:
            if self.dictionary in known_dictionaries:
                self.words = known_dictionaries[self.dictionary]
            else:
                if not self.dictionary:
                    self.dictionary = get_setting('dictionary_file')
                if not self.dictionary or self.dictionary in ['default']:
                    self.words = WORDS
                elif self.dictionary in ['bip39', 'mnemonic']:
                    self.words = BIP39_WORDS
                else:
                    try:
                        path = to_path(self.dictionary)
                        contents = path.read_text()
                    except OSError as e:
                        raise PasswordError(os_error(e))
                    self.words = contents.split()
                known_dictionaries[self.dictionary] = self.words
        return self.words class ObscuredSecret(object):
    """Base class for obscured secrets."""

    # obscurers() {{{2
    @classmethod
    def obscurers(cls):
        for sub in sorted(cls.__subclasses__(), key=lambda s: s.__name__):
            if hasattr(sub, 'conceal') and hasattr(sub, 'reveal'):
                yield sub
                for each in sub.obscurers():
                    if hasattr(each, 'conceal') and hasattr(each, 'reveal'):
                        yield each

    # get_name() {{{2
    @classmethod
    def get_name(cls):
        try:
            return cls.NAME.lower()
        except AttributeError:
            # consider converting lower to upper case transitions in __name__ to
            # dashes.
            return cls.__name__.lower()

    # get_description() {{{2
    @classmethod
    def get_description(cls):
        return None

    # hide() {{{2
    @classmethod
    def hide(cls, text, encoding=None, decorate=False, symmetric=False, gpg_ids=None):
        encoding = encoding.lower() if encoding else 'base64'
        for obscurer in cls.obscurers():
            if encoding == obscurer.get_name():
                return obscurer.conceal(
                    text, decorate, symmetric=symmetric, gpg_ids=gpg_ids
                )
        raise PasswordError('not found.', culprit=encoding)

    # show() {{{2
    @classmethod
    def show(cls, text):
        match = DECORATED_LIST.match(text)
        if match:
            name = match.group(1)
            value = ''.join([s.strip('"' "'") for s in match.group(2).split()])

            for obscurer in cls.obscurers():
                if name == obscurer.__name__:
                    return obscurer.reveal(value)
            raise PasswordError('not found.', culprit=name)

        match = DECORATED_TEXT.match(text)
        if match:
            name = match.group(1)
            value = match.group(2)

            for obscurer in cls.obscurers():
                if name == obscurer.__name__:
                    return obscurer.reveal(value.strip('"' "'"))
            raise PasswordError('not found.', culprit=name)

        return Hidden.reveal(text)

    # encodings() {{{2
    @classmethod
    def encodings(cls):
        for c in cls.obscurers():
            yield c.get_name(), dedent(getattr(c, 'DESC', '')).strip()

    # default encoding() {{{2
    @classmethod
    def default_encoding(cls):
        return Hidden.NAME

    # __repr__() {{{2
    def __repr__(self):
        secret = ObscuredSecret.hide(self.plaintext, 'base64')
        if hasattr(self, 'is_secret') and not self.is_secret:
            return "Hidden('{}', is_secret=False)".format(secret)
        else:
            return "Hidden('{}')".format(secret)

    # __str__() {{{2
    def __str__(self):
        return self.render() def error_source():
    """Source of error
    Reads stack trace to determine filename and line number of error.
    """
    import traceback
    try:
        # return filename and lineno
        # context and content are also available
        exc_cls, exc, tb = sys.exc_info()
        trace = traceback.extract_tb(tb)
        filename, line, context, text = trace[-1]
    except SyntaxError:
        # extract_stack() does not work on binary encrypted files. It generates
        # a syntax error that indicates that the file encoding is missing
        # because the function tries to read the file and sees binary data.
        # This is not a problem with ascii encrypted files as we don't actually
        # show code, which is gibberish, but does not require an encoding. In
        # this case, extract the line number from the trace.
        from .gpg import get_active_python_file
        filename = get_active_python_file()
        line = tb.tb_next.tb_lineno
    except IndexError:
        return None
    return filename, 'line %s' % line
from .charsets import (
    ALPHANUMERIC, DIGITS, DISTINGUISHABLE, LOWERCASE, SYMBOLS, UPPERCASE, SHIFTED
)
from .config import get_setting
from .error import PasswordError
from .dictionary import Dictionary
from .obscure import ObscuredSecret
from .utilities import error_source
from inform import conjoin, cull, log, output, terminate, warn, is_str
import math
import hashlib
import getpass
import re
        import arrow
    import doctest
        self.is_secret = is_secret


# Question {{{1
class Question(Passphrase):
    """Generate arbitrary answer to a given question.

    Similar to Passphrase() except a question must be specified when created
    and it is taken to be the security question. The question is used as a seed
    rather than the field name when generating the secret.

    Args:
        question (str):
            The question to be answered. Be careful. Changing the question in
            any way will change the resulting answer.
        length (int):
            The number of items to draw from the alphabet when creating the
            answer.
        dictionary (str, [str], or callable):
            The reservoir of legal symbols to use when creating the
            password. If not given, or if 'default' is given, this is a
            predefined list of 10,000 words. If given as 'bip39' or
            'mnemonic', this is a predefined list of the 2048 bitcoin BIP-39
            seed words.  Any other string is treated as a path to a file
            that would contain the words. A list is taken as is. Finally,
            you can pass a function that returns the list of words, in which
            case the calling of the function is deferred until the words are
            needed, which is helpful if creating the list is slow.
        master (str):
            Overrides the master seed that is used when generating the password.
            Generally, there is one master seed shared by all accounts contained
            in an account file.  This argument overrides that behavior and
            instead explicitly specifies the master seed for this secret.
        version (str):
            An optional seed. Changing this value will change the generated
            password.
        sep (str):
            A string that is placed between each symbol in the generated
            password.
        prefix (str):
            A string added to the front of the generated password.
        suffix (str):
            A string added to the end of the generated password.
        answer (str):
            The answer. If provided, this would override the generated answer.
            May be a string, or it may be an Obscured object.
        is_secret (bool):
            Should value be hidden from user unless explicitly requested.

    Raises:
        :exc:`avendesora.SecretExhausted`:
            The available entropy has been exhausted.
            This occurs when the requested length is too long.

    Example:

        >>> secret = Question('What city were you born in?')
        >>> secret.initialize(account, 'dux')
        >>> str(secret)
        'dustcart olive label'

    """
    # Generally the user will want to give several security questions, which
    # they would do as an array. It might be tempting to use a dictionary, but
    # that would be undesirable because ...
    # 1. they would have to give the key twice (it is needed as a seed)
    #    actually this is not necessary, could count on order to distinguish
    #    questions, in this way the questions themselves become purely
    #    descriptive, and the answers would change if you changed their order.
    # 2. they would lose the index and any sense of order, so when they wanted
    #    secret, they would have to identify it by typing in the entire question
    #    exactly.
    # constructor {{{2
    def __init__(self,
        question,
        length = 3,
        *,
        answer = None,
        dictionary = None,
        master = None,
        version = None,
        sep = ' ',
        prefix = '',
        suffix = '',
        is_secret = True,
    ):
        self.question = question
        try:
            self.length = int(length)
        except ValueError:
            raise PasswordError(
                'expecting an integer for length.', culprit=error_source()
            )
        if not dictionary or is_str(dictionary):
            self.alphabet = Dictionary(dictionary).get_words
        else:
            self.alphabet = dictionary
        self.master = master
        self.version = version
        self.shift_sort = False
        self.sep = sep
        self.prefix = prefix
        self.suffix = suffix
        self.is_secret = is_secret
        if answer:
            # answer allows the user to override the generator and simply
            # specify the answer. This is also used when producing the archive.
            self.secret = str(answer)

    # get_key_seed() {{{2
    def get_key_seed(self, default=None):
        return self.question

    # get_description() {{{2
    def get_description(self):
        return self.get_key_seed(None)

    # __repr__() {{{2
    def __repr__(self):
        return "Question(%r, answer=Hidden(%r))" % (

            self.question, ObscuredSecret.hide(str(self))