Coverage for /home/ken/.local/lib/python3.6/site-packages/avendesora/account.py : 43%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
# Account # API for an account
# License {{{1 # Copyright (C) 2016 Kenneth S. Kundert # # This program is free software: you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation, either version 3 of the License, or (at your option) any later # version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program. If not, see http://www.gnu.org/licenses/.
# Imports {{{1 Color, conjoin, cull, Error, is_collection, is_str, log, output, warn, indent ) except ImportError: from urlparse import urlparse
# Globals {{{1 color=get_setting('label_color'), scheme=get_setting('color_scheme'), enable=Color.isTTY() )
# AccountValue class {{{1 """An Account Value
Contains three attributes: value: the actual value is_secret: whether the value is secret or contains a secret label: a descriptive name for the value if the value of a simple field is requested """
return str(self.value)
if self.label is not None: return self.label + sep + str(self.value) return str(self.value)
for each in [self.value, self.is_secret, self.label]: yield each
# Account class {{{1 # prevents master password from being added to this base class
# all_accounts() {{{2 def all_accounts(cls): yield each
# fields() {{{2 def fields(cls): for key, value in cls.__dict__.items(): if not key.startswith('_'): yield key, value
# get_name() {{{2 def get_name(cls): # consider converting lower to upper case transitions in __name__ to # dashes.
# get_seed() {{{2 def get_seed(cls):
# override_master() {{{2 def request_seed(cls):
# add_fileinfo() {{{2 def add_fileinfo(cls, master, fileinfo): else:
# matches_exactly() {{{2 def matches_exactly(cls, account): return True
# id_contains() {{{2 def id_contains(cls, target): target = target.lower() if target in cls.get_name().lower(): return True try: for alias in Collection(cls.aliases): if target in alias.lower(): return True except AttributeError: pass return False
# account_contains() {{{2 def account_contains(cls, target): if cls.id_contains(target): return True target = target.lower() for key, value in cls.fields(): if key in TOOL_FIELDS: continue try: if is_collection(value): for k, v in Collection(value).items(): if target in v.lower(): return True elif target in value.lower(): return True except AttributeError: # is not a string, and so pass return False
# recognize() {{{2 def recognize(cls, data, verbose): # try the specified recognizers discovery = getattr(cls, 'discovery', ()) for recognizer in Collection(discovery): if isinstance(recognizer, Recognizer): script = recognizer.match(data, cls, verbose) name = getattr(recognizer, 'name', None) if script: yield name, script if discovery: return
# If no recognizers specified, just check the urls for url in Collection(cls.get_field('urls', default=[])): components = urlparse(url) protocol = components.scheme host = components.netloc if host == data.get('host'): if ( protocol != data.get('protocol') and data['protocol'] in get_setting('required_protocols') ): msg = 'url matches, but uses wrong protocol.' notify(msg) raise Error(msg, culprit=account.get_name()) else: yield None, True return
# initialize() {{{2 if not cls._file_info.encrypted: warn( 'high value master password not contained in encrypted', 'account file.', culprit=cls.get_name() ) except AttributeError as err: pass
# items() {{{2 def items(cls): for key in sorted(cls.__dict__): if not key.startswith('_'): yield key, cls.__dict__[key]
# get_field() {{{2 "Get field Value given a field name and key" raise Error( 'not found.', culprit=(cls.get_name(), cls.combine_name(name, key)) ) else:
choices = [] for k, v in Collection(value).items(): try: choices.append(' %s: %s' % (k, v.get_key())) except AttributeError: choices.append(' %s:' % k) raise Error( 'composite value found, need key. Choose from:', *choices, sep='\n', culprit=name, is_collection=True, collection = value ) else: else: warn('not a composite value, key ignored.', culprit=name) key = None except (IndexError, KeyError, TypeError): raise Error('not found.', culprit=cls.combine_name(name, key))
# generate the value if needed
# is_secret() {{{2 else: except (IndexError, KeyError, TypeError): raise Error('not found.', culprit=cls.combine_name(name, key))
# split_name() {{{2 def split_name(cls, name): # Account fields can either be scalars or composites (vectors or # dictionaries). This function takes a string (name) that the user # provides to specify which account value they wish and splits it into a # field name and a key. If the field is a scalar, the key will be None. # Users request a value using one of the following forms: # True: use default name # field: scalar value (key=None) # index: questions (field->'questions', key=index) # field[index] or field/index: for vector value # field[key] or field/key: for dictionary value
# convert dashes to underscores
# If name is an integer, treat it as number of security question.
# Split name if given in the form: name/key
# Split name if given in the form: name[key] # vector name using 'name[key]' syntax name, key = match.groups() try: return name, int(key) except ValueError: return name, key
# Must be scalar name
# combine_name() {{{2 # Inverse of split_name().
# convert underscores to dashes #name = name.replace('_', '-')
else:
# get_value() {{{2 """Get Account Value
Return value from the account given a user friendly identifier or script. User friendly identifiers include: None: value of default attribute name: scalar value name.key or name[key]: member of a dictionary or array key is string for dictionary, integer for array Scripts are simply strings with embedded attributes. Ex: 'username: {username}, password: {passcode}' Returns a tuple: value, is_secret, label """
# get default if field was not given
# treat field as name rather than script if it there are no attributes except Error as err: err.terminate()
# run the script script = field regex = re.compile(r'({[\w. ]+})') out = [] is_secret = False for term in regex.split(script): if term and term[0] == '{' and term[-1] == '}': # we have found a command cmd = term[1:-1].lower() if cmd == 'tab': out.append('\t') elif cmd == 'return': out.append('\n') elif cmd.startswith('sleep '): pass else: name, key = cls.split_name(cmd) try: value = cls.get_field(name, key) out.append(dedent(str(value)).strip()) if cls.is_secret(name, key): is_secret = True except Error as err: err.terminate() else: out.append(term) return AccountValue(''.join(out), is_secret)
value = cls.get_field(*cls.split_name(name)) return value
# write_summary() {{{2 def write_summary(cls): # present all account values that are not explicitly secret to the user
def fmt_field(key, value='', level=0): if '\n' in value: value = indent(dedent(value), get_setting('indent')).strip('\n') sep = '\n' elif value: sep = ' ' else: sep = '' key = str(key).replace('_', ' ') leader = level*get_setting('indent') return indent(LabelColor(key + ':') + sep + value, leader)
def reveal(name, key=None): return "<reveal with 'avendesora value %s %s'>" % ( cls.get_name(), cls.combine_name(name, key) )
def extract_collection(name, collection): lines = [fmt_field(key)] for k, v in Collection(collection).items(): if hasattr(v, 'generate'): # is a secret, get description if available try: v = '%s %s' % (v.get_key(), reveal(name, k)) except AttributeError: v = reveal(name, k) lines.append(fmt_field(k, v, level=1)) return lines
# preload list with the names associated with this account names = [cls.get_name()] if hasattr(cls, 'aliases'): names += Collection(cls.aliases) lines = [fmt_field('names', ', '.join(names))]
for key, value in cls.items(): if key in TOOL_FIELDS: pass # is an Avendesora field elif is_collection(value): lines += extract_collection(key, value) elif hasattr(value, 'generate'): lines.append(fmt_field(key, reveal(key))) else: lines.append(fmt_field(key, value)) output(*lines, sep='\n')
# archive() {{{2 def archive(cls): # return all account fields along with their values as a dictionary
def extract(value, name, key=None): if not is_collection(value): if hasattr(value, 'generate'): value.generate(name, key, cls) #value = 'Hidden(%s)' % Obscure.hide(str(value)) return value try: return {k: extract(v, name, k) for k, v in value.items()} except AttributeError: # still need to work out how to output the question. return [extract(v, name, i) for i, v in enumerate(value)]
return {k: extract(v, k) for k, v in cls.items() if k != 'master'}
# open_browser() {{{2 if not browser_name: browser_name = cls.get_field('browser', default=None) browser = StandardBrowser(browser_name)
# get the urls from the urls attribute if not key: key = getattr(cls, 'default_url', None) urls = getattr(cls, 'urls', []) if type(urls) != dict: if is_str(urls): urls = urls.split() urls = {None: urls}
# get the urls from the url recognizers # currently urls from recognizers dominate over those from attributes discovery = getattr(cls, 'discovery', ()) for each in Collection(discovery): urls.update(each.all_urls())
# select the urls try: urls = urls[key] except TypeError: if key: raise Error( 'keys are not supported with urls on this account.', culprit=key ) except KeyError: keys = cull(urls.keys()) if keys: raise Error( 'unknown key, choose from %s.' % conjoin(keys), culprit=key ) else: raise Error( 'keys are not supported with urls on this account.', culprit=key ) url = list(Collection(urls))[0] # use the first url specified
# open the url browser.run(url)
# StealthAccount class {{{1 # prevents master password from being added to this base class
def get_seed(cls): # need to handle case where stdin/stdout is not available. # perhaps write generic password getter that supports both gui and tui. # Then have global option that indicates which should be used. # Separate name from seed. Only request seed when generating a password. import getpass try: name = getpass.getpass('account name: ') except EOFError: output() name = '' if not name: warn('null account name.') return name
def archive(cls): # do not archive stealth accounts pass
|