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_composite('urls', default=[])): url = str(url) # this is needed because url may be Script url = url if '//' in url else ('//' + url) url_components = urlparse(url) if url_components.scheme: protocol = url_components.scheme else: protocol = get_setting('default_protocol') host = url_components.netloc if host == data.get('host'): if (protocol == data.get('protocol')): if verbose: log(' %s: matches.' % cls.get_name()) yield None, True return else: msg = 'URL matches, but uses wrong protocol.' notify(msg) raise PasswordError(msg, culprit=cls.get_name())
def discover_account(self, title=None, verbose=False): log('Account Discovery ...') if get_setting('verbose'): verbose = True # get and parse the title data = Title(override=title).get_data() # sweep through accounts to see if any recognize this title data matches = {} for account in self.all_accounts: name = account.get_name() if verbose: log('Trying:', name) for key, script in account.recognize(data, verbose): ident = '%s (%s)' % (name, key) if key else name matches[ident] = name, script if verbose: log(' %s matches' % ident) if not matches: msg = 'cannot find appropriate account.' notify(msg) raise Error(msg) if len(matches) > 1: choice = show_list_dialog(sorted(matches.keys())) return matches[choice] else: return matches.popitem()[1]
def match(self, given, account, verbose=False): try: for url in self.urls: url = url if '//' in url else ('//' + url) url_components = urlparse(url) if url_components.scheme: protocol = url_components.scheme else: protocol = get_setting('default_protocol') host = url_components.netloc path = url_components.path # given may contain the following fields after successful title # recognition: # rawdata: the original title # title: the processed title # url: the full url # browser: the name of the browser # protocol: the url scheme (ex. http, https, ...) # host: the url host name or IP address # path: the path component of the url # does not include options or anchor if host == given.get('host'): def path_matches(expected, actual): if not expected: # path was not specified, treat it as don't care return True if self.exact_path: # exact path match expected return expected == actual else: # otherwise just match what was given return actual.startswith(expected) if path_matches(path, given.get('path')): if self.fragment and given.get( 'fragment') != self.fragment: continue if (protocol == given.get('protocol')): if verbose: log(' %s: matches.' % self.get_name()) return self.script else: msg = 'url matches, but uses wrong protocol.' notify(msg) raise PasswordError(msg, culprit=account.get_name()) except Exception as e: raise PasswordError(str(e), culprit=e.__class__.__name__) if verbose: log(' %s: no match.' % self.get_name())
def render_script(account, field): # if field was not given if not field: name, key = account.split_field(field) field = '.'.join(cull([name, key])) # treat field as name rather than script if it there are not attributes if '{' not in field: name, key = account.split_field(field) value = account.get_scalar(name, key) is_secret = account.is_secret(name, key) label = account.combine_field(name, key) try: alt_name = value.get_key() if alt_name: label += ' (%s)' % alt_name except AttributeError: pass return dedent(str(value)).strip(), is_secret, label script = field # Run the script 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().strip() if cmd == 'tab': out.append('\t') elif cmd == 'return': out.append('\n') elif cmd.startswith('sleep '): pass elif cmd.startswith('rate '): pass elif cmd.startswith('remind '): notify(term[8:-1].strip()) else: if cmd.startswith('paste '): cmd = cmd[6:] name, key = account.split_field(cmd.strip()) value = account.get_scalar(name, key) out.append(dedent(str(value)).strip()) if account.is_secret(name, key): is_secret = True else: out.append(term) return ''.join(out), is_secret, None
def run_script(self, script, dryrun=False): out = [] scrubbed = [] sleep(INITIAL_AUTOTYPE_DELAY) ms_per_char = None for cmd, val in script.components(True): if cmd in ['tab', 'return', 'text', 'value']: out.append(val) scrubbed.append(val) elif cmd.startswith('sleep '): scrubbed.append('<%s>' % cmd) try: kw, seconds = cmd.split() assert kw == 'sleep' if out: # drain the buffer before sleeping if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] sleep(float(seconds)) except (TypeError, ValueError): raise PasswordError('syntax error in keyboard script.', culprit=cmd) elif cmd.startswith('rate '): scrubbed.append('<%s>' % cmd) try: kw, rate = cmd.split() assert kw == 'rate' if out: # drain the buffer before changing rate if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] ms_per_char = int(rate) except (TypeError, ValueError): raise PasswordError('syntax error in keyboard script.', culprit=cmd) elif cmd.startswith('remind '): notify(cmd[7:]) else: out.append(val) scrubbed.append('<%s>' % cmd) scrubbed = ''.join(scrubbed).replace('\t', '→').replace('\n', '↲') log('Autotyping "%s"%s.' % (scrubbed, ' -- dry run' if dryrun else '')) if dryrun: output(script.account.get_name(), scrubbed, sep=': ') else: self._autotype(''.join(out), ms_per_char)
def match(self, data, account, verbose=False): try: for url in self.urls: url = urlparse(url) protocol = url.scheme host = url.netloc path = url.path # data may contain the following fields after successful title # recognition: # rawdata: the original title # title: the processed title # url: the full url # browser: the name of the browser # protocol: the url scheme (ex. http, https, ...) # host: the url host name or IP address # path: the path component of the url # does not include options or anchor if host == data.get('host'): def path_matches(expected, actual): if not expected: # path was not specified, treat it as don't care return True if self.exact_path: # exact path match expected return expected == actual else: # otherwise just match what was given return actual.startswith(expected) if path_matches(path, data.get('path')): if ( protocol == data.get('protocol') or protocol not in REQUIRED_PROTOCOLS ): if verbose: log(' %s: matches.' % self.get_name()) return self.script else: msg = 'url matches, but uses wrong protocol.' notify(msg) raise Error(msg, culprit=account.get_name()) except Exception as err: raise Error(str(err), culprit=err.__class__.__name__) if verbose: log(' %s: no match.' % self.get_name())
def components(self, ask=False): """Iterates through the script. Yields a tuple for each component of a script. The tuple consists of the type of the component and the value of the component. The type may be 'tab' (a tab character), 'return' (a return character), 'text' (raw text), 'value' (the value of a field that is not a secret), 'sleep N' (a request to sleep N seconds), 'rate N' (set the autotype to 1 keystroke every N milliseconds), and finally a field name (the value of a field that is secret).. Args: ask (bool): Determines what happens when a composite field is encountered. If ask is true a dialog window is opened that that allows the user to select the desired member of the collection. If false, a PasswordError is raised. Raises: :exc:`avendesora.PasswordError`: attribute not found. """ account = self.account for term in self.SPLITTER.split(self.script): if term and term[0] == '{' and term[-1] == '}': # we have found a command cmd = term[1:-1].lower() if cmd == 'tab': val = '\t' elif cmd == 'return': val = '\n' elif cmd.startswith('sleep '): val = '' elif cmd.startswith('rate '): val = '' elif cmd.startswith('remind '): notify(cmd[7:]) val = '' else: if cmd.startswith('paste '): _, field = cmd.split(maxsplit=1) else: field = cmd cmd = None name, key = account.split_field(field) try: value = account.get_scalar(name, key) except PasswordError as e: if ask and e.is_collection and len(e.collection): # is composite value, ask user which one is desired choices = e.collection if len(choices) == 1: choice = choices.keys()[0] else: choice = show_list_dialog( 'Choose from %s' % name, sorted(choices.keys())) if choice is None: raise PasswordError( 'user abort.', culprit=(account.get_name(), account.combine_field( name, key))) key = choices[choice] value = account.get_scalar(name, key) else: raise try: val = dedent(str(value)).strip() except RecursionError: raise PasswordError( 'script must not reference itself.', culprit=field) if not cmd: if account.is_secret(name, key): cmd = 'secret' else: cmd = 'value' else: cmd = 'text' val = term if not term: continue yield cmd, val
def show_list_dialog(options): msg = 'selection dialog not available, you must install python3-gobject.' notify(msg) fatal(msg)
def show_error_dialog(message): msg = 'error dialog not available, you must install python3-gobject.' notify(msg) fatal(msg)
def run_script(self, script, dryrun=False): out = [] scrubbed = [] sleep(INITIAL_AUTOTYPE_DELAY) ms_per_char = None try: for cmd, val in script.components(True): if cmd in ['tab', 'return', 'text', 'value']: out.append(val) scrubbed.append(val) elif cmd.startswith('sleep '): scrubbed.append('<%s>' % cmd) kw, seconds = cmd.split() if out: # drain the buffer before sleeping if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] sleep(float(seconds)) elif cmd.startswith('rate '): scrubbed.append('<%s>' % cmd) kw, rate = cmd.split() if out: # drain the buffer before changing rate if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] ms_per_char = int(rate) elif cmd.startswith('paste '): # This is an experiment. It is an attempt to get wellsfargo # to work without forcing me to solve captchas. The idea is # that if wellsfargo is timing my keystrokes, then I could # try replacing the typing with a paste operation. To make # this work, you have to click on the password field and # then hover over the password field before typing the # autotype hotkey. scrubbed.append('<%s>' % cmd) kw, field = cmd.split() if out: # drain the buffer before pasting if not dryrun: self._autotype(''.join(out), ms_per_char) out = [] if not dryrun: log(f'Writing {field} to primary selection and pasting.') xsel = split_cmd(get_setting('xsel_executable')) xdotool = split_cmd(get_setting('xdotool_executable')) try: Run(xsel + ['--input', '--primary'], 'soEW', stdin=val) sleep(0.1) Run(xdotool + ['click', '--clearmodifiers', '2'], 'soEW') finally: sleep(0.1) Run(xsel + ['--clear', '--primary'], 'soEW') elif cmd.startswith('remind '): notify(cmd[7:]) else: out.append(val) scrubbed.append('<%s>' % cmd) except (TypeError, ValueError): raise PasswordError( 'syntax error in keyboard script.', culprit=cmd ) scrubbed = ''.join(scrubbed).replace('\t', '→').replace('\n', '↲') log(f'Autotyping "{scrubbed}"{" -- dry run" if dryrun else ""}.') if dryrun: output(script.account.get_name(), scrubbed, sep=': ') else: self._autotype(''.join(out), ms_per_char)