Merge pull request #17 from jnphilipp/copy-fields

Copy other fields
This commit is contained in:
Jonathan Lestrelin 2019-09-23 12:42:34 +00:00 committed by GitHub
commit f12076dc51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 63 additions and 25 deletions

View File

@ -41,6 +41,19 @@ The search provider should show up and be enabled in GNOME search preferences an
The [pass-otp](https://github.com/tadfisher/pass-otp) extension is supported. Searches starting with `otp` will copy the otp token to the clipboard. The [pass-otp](https://github.com/tadfisher/pass-otp) extension is supported. Searches starting with `otp` will copy the otp token to the clipboard.
# Fields
To copy other values than the password in the first line from a pass file, start the search with `:NAME search...`. The field name must be a full but case insensitive match. This requires `GPaste`.
For example with a pass file like:
```
SUPERSECRETPASSWORD
user: username
pin: 123456
```
To copy the pin start the search with `:pin` and for the username `:user`.
# Environment variables # Environment variables
If you are configuring pass through environment variables, such as `PASSWORD_STORE_DIR`, make sure to set them in a way that will propagate to the search provider executable, not just in your shell. If you are configuring pass through environment variables, such as `PASSWORD_STORE_DIR`, make sure to set them in a way that will propagate to the search provider executable, not just in your shell.

View File

@ -25,18 +25,17 @@
# Copyright (C) 2012 Red Hat, Inc. # Copyright (C) 2012 Red Hat, Inc.
# Author: Luke Macken <lmacken@redhat.com> # Author: Luke Macken <lmacken@redhat.com>
from fuzzywuzzy import process, fuzz
from os import getenv
from os import walk
from os.path import expanduser
from os.path import join as path_join
import re
import subprocess
import dbus import dbus
import dbus.glib import dbus.glib
import dbus.service import dbus.service
import re
import subprocess
from os import getenv, walk
from os.path import expanduser, join as path_join
from gi.repository import GLib from gi.repository import GLib
from fuzzywuzzy import process, fuzz
# Convenience shorthand for declaring dbus interface methods. # Convenience shorthand for declaring dbus interface methods.
# s.b.n. -> search_bus_name # s.b.n. -> search_bus_name
@ -52,7 +51,6 @@ class SearchPassService(dbus.service.Object):
""" """
bus_name = 'org.gnome.Pass.SearchProvider' bus_name = 'org.gnome.Pass.SearchProvider'
_object_path = '/' + bus_name.replace('.', '/') _object_path = '/' + bus_name.replace('.', '/')
def __init__(self): def __init__(self):
@ -72,7 +70,8 @@ class SearchPassService(dbus.service.Object):
@dbus.service.method(in_signature='as', out_signature='aa{sv}', **sbn) @dbus.service.method(in_signature='as', out_signature='aa{sv}', **sbn)
def GetResultMetas(self, ids): def GetResultMetas(self, ids):
return [dict(id=id, name=id, gicon="password-manager") for id in ids] return [dict(id=id, name=id[1:] if id.startswith(':') else id,
gicon='dialog-password') for id in ids]
@dbus.service.method(in_signature='asas', out_signature='as', **sbn) @dbus.service.method(in_signature='asas', out_signature='as', **sbn)
def GetSubsearchResultSet(self, previous_results, new_terms): def GetSubsearchResultSet(self, previous_results, new_terms):
@ -84,14 +83,15 @@ class SearchPassService(dbus.service.Object):
def get_result_set(self, terms): def get_result_set(self, terms):
if terms[0] == 'otp': if terms[0] == 'otp':
otp = True field = terms[0]
elif terms[0].startswith(':'):
field = terms[0][1:]
terms = terms[1:] terms = terms[1:]
else: else:
otp = False field = None
name = ''.join(terms) name = ''.join(terms)
password_list = [] password_list = []
for root, dirs, files in walk(self.password_store): for root, dirs, files in walk(self.password_store):
dir_path = root[len(self.password_store) + 1:] dir_path = root[len(self.password_store) + 1:]
@ -106,16 +106,28 @@ class SearchPassService(dbus.service.Object):
results = [e[0] for e in process.extract(name, password_list, limit=5, results = [e[0] for e in process.extract(name, password_list, limit=5,
scorer=fuzz.partial_ratio)] scorer=fuzz.partial_ratio)]
if otp: if field == 'otp':
results = [f'otp {r}' for r in results] results = [f'otp {r}' for r in results]
elif field is not None:
results = [f':{field} {r}' for r in results]
return results return results
def send_password_to_gpaste(self, base_args, name): def send_password_to_gpaste(self, base_args, name, field=None):
try: try:
pass_output = subprocess.check_output(base_args + [name], output = subprocess.check_output(base_args + [name],
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
universal_newlines=True) universal_newlines=True)
password = pass_output.split('\n', 1)[0] if field is not None:
match = re.search(fr'^{field}:\s*(?P<value>.+?)$', output,
flags=re.I|re.M)
if match:
password = match.group('value')
else:
raise RuntimeError(f'The field {field} was not found in ' +
'the pass file.')
else:
password = output.split('\n', 1)[0]
self.session_bus.get_object( self.session_bus.get_object(
'org.gnome.GPaste.Daemon', 'org.gnome.GPaste.Daemon',
'/org/gnome/GPaste' '/org/gnome/GPaste'
@ -128,14 +140,23 @@ class SearchPassService(dbus.service.Object):
if 'otp' in base_args: if 'otp' in base_args:
self.notify('Copied OTP password to clipboard:', self.notify('Copied OTP password to clipboard:',
body=f'<b>{name}</b>') body=f'<b>{name}</b>')
elif field is not None:
self.notify(f'Copied field {field} to clipboard:',
body=f'<b>{name}</b>')
else: else:
self.notify('Copied password to clipboard:', self.notify('Copied password to clipboard:',
body=f'<b>{name}</b>') body=f'<b>{name}</b>')
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as e:
self.notify('Failed to copy password!', body=error.output, self.notify('Failed to copy password!', body=e.output, error=True)
error=True) except RuntimeError as e:
self.notify('Failed to copy field!', body=e.output, error=True)
def send_password_to_native_clipboard(self, base_args, name, field=None):
if field is not None:
self.notify(f'Cannot copy field values.',
body='This feature requires GPaste.', error=True)
return
def send_password_to_native_clipboard(self, base_args, name):
pass_cmd = subprocess.run(base_args + ['-c', name]) pass_cmd = subprocess.run(base_args + ['-c', name])
if pass_cmd.returncode: if pass_cmd.returncode:
self.notify('Failed to copy password!', error=True) self.notify('Failed to copy password!', error=True)
@ -146,18 +167,22 @@ class SearchPassService(dbus.service.Object):
self.notify('Copied password to clipboard:', body=f'<b>{name}</b>') self.notify('Copied password to clipboard:', body=f'<b>{name}</b>')
def send_password_to_clipboard(self, name): def send_password_to_clipboard(self, name):
field = None
if name.startswith('otp '): if name.startswith('otp '):
base_args = ['pass', 'otp', 'code'] base_args = ['pass', 'otp', 'code']
name = name[4:] name = name[4:]
else: else:
base_args = ['pass', 'show'] base_args = ['pass', 'show']
if name.startswith(':'):
field, name = name.split(' ', 1)
field = field[1:]
try: try:
self.send_password_to_gpaste(base_args, name) self.send_password_to_gpaste(base_args, name, field)
except dbus.DBusException: except dbus.DBusException:
# We couldn't join GPaste over D-Bus, # We couldn't join GPaste over D-Bus,
# use pass native clipboard copy # use pass native clipboard copy
self.send_password_to_native_clipboard(base_args, name) self.send_password_to_native_clipboard(base_args, name, field)
def notify(self, message, body='', error=False): def notify(self, message, body='', error=False):
try: try: