Refactor: move all pass related non gui stuff to module

This commit refactors this program to use a separate module for pass
related stuff. This will enable a better separation between backend
and frontend.

There is still some work to do, e.g. adding getters and setters
instead of using member attributes directly, but it is a start.
main
Micke Nordin 3 years ago
parent d6b0842991
commit 49473c85bf
Signed by: micke
GPG Key ID: 014B273D614BE877

@ -1,18 +1,20 @@
#!/usr/bin/env python3 #!/usr/bin/python3
""" """
A mobile first interface for the standard unix password manager written in python A mobile first interface for the standard unix password manager written in python
""" """
import os import os
import subprocess
import wx import wx
import wx.lib.scrolledpanel as scrolled import wx.lib.scrolledpanel as scrolled
from pass_handler import Pass, copy_to_clipboard, get_password_from_path, pass_pull, pass_push
class PassUi(wx.Frame): class PassUi(wx.Frame):
""" """
The wx.Frame for passui The wx.Frame for passui
""" """
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
"""__init__. """__init__.
@ -20,33 +22,27 @@ class PassUi(wx.Frame):
:param kw: :param kw:
""" """
super().__init__(*args, **kw) super().__init__(*args, **kw)
# Find the top directory and set that to current directory self.pass_handler = Pass()
self.topdir = os.environ.get('PASSWORD_STORE_DIR')
if not self.topdir:
self.topdir = os.environ.get('HOME') + '/.password-store'
self.curdir = self.topdir
# create a panel in the frame # create a panel in the frame
self.pnl = scrolled.ScrolledPanel(self, -1, style=wx.VSCROLL) self.pnl = scrolled.ScrolledPanel(self, -1, style=wx.VSCROLL)
self.pnl.SetupScrolling() self.pnl.SetupScrolling()
# and create a sizer to manage the layout of child widgets # and create a sizer to manage the layout of child widgets
self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = wx.BoxSizer(wx.VERTICAL)
self.pnl.SetSizer(self.sizer) self.pnl.SetSizer(self.sizer)
self.cur_paths = self.get_pass_paths()
self.cur_passwords = self.get_pass_passwords()
self.add_buttons() self.add_buttons()
def add_buttons(self): def add_buttons(self):
"""add_buttons.""" """add_buttons."""
self.sizer.Clear(delete_windows=True) self.sizer.Clear(delete_windows=True)
self.add_tools() self.add_tools()
if self.curdir != self.topdir: if self.pass_handler.cur_dir != self.pass_handler.top_dir:
btn = self.make_back_button() btn = self.make_back_button()
self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member
self.Bind(wx.EVT_BUTTON, self.Bind(wx.EVT_BUTTON,
lambda event: self.path_button_clicked(), btn) lambda event: self.path_button_clicked(), btn)
index = 0 index = 0
for cpath in self.cur_paths: for cpath in self.pass_handler.cur_paths:
if cpath != self.curdir: if cpath != self.pass_handler.cur_dir:
label = '🗀 ' + os.path.basename(os.path.normpath(cpath)) label = '🗀 ' + os.path.basename(os.path.normpath(cpath))
btn = wx.Button(self.pnl, label=label) btn = wx.Button(self.pnl, label=label)
self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member
@ -56,7 +52,7 @@ class PassUi(wx.Frame):
btn) btn)
index = index + 1 index = index + 1
index = 0 index = 0
for password in self.cur_passwords: for password in self.pass_handler.cur_passwords:
label = '🗝 ' + os.path.splitext( label = '🗝 ' + os.path.splitext(
os.path.basename(os.path.normpath(password)))[0] os.path.basename(os.path.normpath(password)))[0]
btn = wx.Button(self.pnl, label=label) btn = wx.Button(self.pnl, label=label)
@ -106,7 +102,7 @@ class PassUi(wx.Frame):
:param index: :param index:
""" """
path = self.get_pass_path_from_index(index, "password") path = self.pass_handler.get_pass_path_from_index(index, "password")
dlg = wx.MessageDialog(self.pnl, dlg = wx.MessageDialog(self.pnl,
"Delete " + path + "?", "Delete " + path + "?",
"Are you sure?", "Are you sure?",
@ -115,69 +111,23 @@ class PassUi(wx.Frame):
reply = dlg.ShowModal() reply = dlg.ShowModal()
if reply == wx.ID_CANCEL: if reply == wx.ID_CANCEL:
return return
command1 = ['/usr/bin/pass', 'rm', '-f', path] self.pass_handler.delete_password(path)
run_command(command1)
self.move_up()
self.cur_paths = self.get_pass_paths()
self.cur_passwords = self.get_pass_passwords()
self.back_button_clicked() self.back_button_clicked()
def get_pass_path_from_index(self, index, pathtype="path"):
"""get_pass_path_from_index.
:param index:
:param pathtype:
"""
if pathtype == "password":
result = self.cur_passwords[index]
else:
result = self.cur_paths[index]
return result.replace(self.topdir, '').replace('.gpg', '')
def get_pass_passwords(self):
"""get_pass_passwords."""
passwords = []
for mfile in os.listdir(self.curdir):
if mfile.endswith(".gpg"): # and os.path.isfile(mfile):
passwords.append(os.path.join(self.curdir, mfile))
passwords = sorted(passwords)
return passwords
def get_pass_paths(self):
"""get_pass_paths."""
dirs = []
if self.curdir != self.topdir:
dirs.append(self.curdir)
for cdir in os.listdir(self.curdir):
if os.path.isdir(os.path.join(self.curdir,
cdir)) and cdir != ".git":
dirs.append(os.path.join(self.curdir, cdir))
dirs = sorted(dirs)
return dirs
def make_back_button(self, index=None): def make_back_button(self, index=None):
"""make_back_button. """make_back_button.
:param index: :param index:
""" """
if index is not None: if index is not None:
label = self.get_pass_path_from_index(index, "password") label = self.pass_handler.get_pass_path_from_index(index, "password")
else: else:
label = self.curdir.replace(self.topdir, '') label = self.pass_handler.cur_dir.replace(self.pass_handler.top_dir, '')
btn = wx.Button(self.pnl, label=label + '') btn = wx.Button(self.pnl, label=label + '')
font = btn.GetFont().MakeItalic().MakeBold() font = btn.GetFont().MakeItalic().MakeBold()
btn.SetFont(font) btn.SetFont(font)
return btn return btn
def move_up(self):
"""move_up."""
if self.curdir == self.topdir:
return True
if not os.path.isdir(self.curdir):
self.curdir = os.path.abspath(os.path.join(self.curdir, os.pardir))
self.move_up()
return True
def password_button_clicked(self, index): def password_button_clicked(self, index):
"""password_button_clicked. """password_button_clicked.
@ -191,10 +141,10 @@ class PassUi(wx.Frame):
:param path: :param path:
""" """
if path is None: if path is None:
path = os.path.abspath(os.path.join(self.curdir, os.pardir)) path = os.path.abspath(os.path.join(self.pass_handler.cur_dir, os.pardir))
self.curdir = path self.pass_handler.cur_dir = path
self.cur_paths = self.get_pass_paths() self.pass_handler.cur_paths = self.pass_handler.get_pass_paths()
self.cur_passwords = self.get_pass_passwords() self.pass_handler.cur_passwords = self.pass_handler.get_pass_passwords()
self.add_buttons() self.add_buttons()
def save_to_pass(self, path, text, name=None): def save_to_pass(self, path, text, name=None):
@ -204,10 +154,10 @@ class PassUi(wx.Frame):
:param text: :param text:
:param name: :param name:
""" """
fullpath = os.path.dirname(self.topdir + '/' + path.lstrip('/')) fullpath = os.path.dirname(self.pass_handler.top_dir + '/' + path.lstrip('/'))
if name is not None: if name is not None:
path = name.GetLineText(0) path = name.GetLineText(0)
fullpath = os.path.dirname(self.topdir + '/' + path.lstrip('/')) fullpath = os.path.dirname(self.pass_handler.top_dir + '/' + path.lstrip('/'))
filename = fullpath + '.gpg' filename = fullpath + '.gpg'
if os.path.exists(fullpath) or os.path.exists(filename): if os.path.exists(fullpath) or os.path.exists(filename):
dlg = wx.MessageDialog( dlg = wx.MessageDialog(
@ -222,8 +172,7 @@ class PassUi(wx.Frame):
dlg = wx.MessageDialog(self.pnl, dlg = wx.MessageDialog(self.pnl,
"Save to " + path + "?", "Save to " + path + "?",
"Are you sure?", "Are you sure?",
style=wx.CANCEL | wx.CANCEL_DEFAULT style=wx.CANCEL | wx.CANCEL_DEFAULT | wx.OK)
| wx.OK)
dlg.SetOKCancelLabels("&Yes", "&Don't save") dlg.SetOKCancelLabels("&Yes", "&Don't save")
reply = dlg.ShowModal() reply = dlg.ShowModal()
if reply == wx.ID_CANCEL: if reply == wx.ID_CANCEL:
@ -233,11 +182,7 @@ class PassUi(wx.Frame):
password += text.GetLineText(lineno) password += text.GetLineText(lineno)
password += '\n' password += '\n'
command1 = ['/bin/echo', password.rstrip("\n")] self.pass_handler.save_to_pass(password,path,fullpath)
command2 = ['/usr/bin/pass', 'insert', '-m', path]
run_command(command1, command2)
self.cur_paths = sorted([fullpath] + self.cur_paths)
self.cur_passwords = self.get_pass_passwords()
self.back_button_clicked() self.back_button_clicked()
def show_new_dialog(self): def show_new_dialog(self):
@ -245,11 +190,11 @@ class PassUi(wx.Frame):
""" """
self.sizer.Clear(delete_windows=True) self.sizer.Clear(delete_windows=True)
passpath = self.curdir.replace(self.topdir, '') passpath = self.pass_handler.cur_dir.replace(self.pass_handler.top_dir, '')
btn = self.make_back_button() btn = self.make_back_button()
self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member
self.Bind(wx.EVT_BUTTON, self.Bind(wx.EVT_BUTTON,
lambda event, path=self.curdir: self.path_button_clicked( lambda event, path=self.pass_handler.cur_dir: self.path_button_clicked(
path), path),
btn) btn)
@ -282,8 +227,8 @@ class PassUi(wx.Frame):
self.sizer.Clear(delete_windows=True) self.sizer.Clear(delete_windows=True)
self.add_tools(index) self.add_tools(index)
passpath = self.get_pass_path_from_index(index, "password") passpath = self.pass_handler.get_pass_path_from_index(index, "password")
cpath = self.topdir + os.path.dirname(passpath) cpath = self.pass_handler.top_dir + os.path.dirname(passpath)
password = get_password_from_path(passpath) password = get_password_from_path(passpath)
btn = self.make_back_button(index) btn = self.make_back_button(index)
@ -320,8 +265,8 @@ class PassUi(wx.Frame):
""" """
self.sizer.Clear(delete_windows=True) self.sizer.Clear(delete_windows=True)
self.add_tools(index) self.add_tools(index)
passpath = self.get_pass_path_from_index(index, "password") passpath = self.pass_handler.get_pass_path_from_index(index, "password")
cpath = self.topdir + os.path.dirname(passpath) cpath = self.pass_handler.top_dir + os.path.dirname(passpath)
password = get_password_from_path(passpath) password = get_password_from_path(passpath)
btn = self.make_back_button(index) btn = self.make_back_button(index)
self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member
@ -373,63 +318,6 @@ class PassUi(wx.Frame):
self.sizer.Layout() self.sizer.Layout()
def copy_to_clipboard(text):
"""copy_to_clipboard.
:param text:
"""
password = text.split('\n')[0]
command1 = ['/bin/echo', password]
command2 = ['/usr/bin/wl-copy']
result = run_command(command1, command2)
return result
def get_password_from_path(passpath):
"""get_password_from_path.
:param passpath:
"""
result = run_command(['/usr/bin/pass', 'show', passpath])
password = result[0].decode()
return password
def pass_pull():
"""pass_pull.
"""
run_command(['/usr/bin/pass', 'git', 'pull'])
def pass_push():
"""pass_push.
"""
run_command(['/usr/bin/pass', 'git', 'push'])
def run_command(command1, command2=None):
"""Run a command on system and capture result
:param command1:
:param command2:
"""
process1 = subprocess.Popen(command1,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# If there is a second command it is taken to be a pipline
if command2:
subprocess.Popen(command2,
shell=False,
stdin=process1.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process1.stdout.close()
return process1.communicate()
if __name__ == '__main__': if __name__ == '__main__':
# When this module is run (not imported) then create the app, the # When this module is run (not imported) then create the app, the
# frame, show it, and start the event loop. # frame, show it, and start the event loop.

@ -0,0 +1,132 @@
#!/usr/bin/python3
import os
import subprocess
from typing import Union
class Pass:
def __init__(self):
self.top_dir: str = os.environ.get('PASSWORD_STORE_DIR')
if not self.top_dir:
self.top_dir = os.environ.get('HOME') + '/.password-store'
self.cur_dir: str = self.top_dir
self.cur_paths: list[str] = self.get_pass_paths()
self.cur_passwords: list[str] = self.get_pass_passwords()
def delete_password(self, path: str):
"""delete_password.
:param path: str
"""
command1: list[str] = ['/usr/bin/pass', 'rm', '-f', path]
run_command(command1)
self.move_up()
self.cur_paths = self.get_pass_paths()
self.cur_passwords = self.get_pass_passwords()
def get_pass_path_from_index(self, index: int, path_type: str = "path") -> str:
"""get_pass_path_from_index.
:param index: int
:param path_type: str, default: "path"
"""
if path_type == "password":
result: str = self.cur_passwords[index]
else:
result: str = self.cur_paths[index]
return result.replace(self.top_dir, '').replace('.gpg', '')
def get_pass_passwords(self) -> list[str]:
"""get_pass_passwords."""
passwords: list[str] = list()
for m_file in os.listdir(self.cur_dir):
if m_file.endswith(".gpg"):
passwords.append(os.path.join(self.cur_dir, m_file))
passwords = sorted(passwords)
return passwords
def get_pass_paths(self) -> list[str]:
"""get_pass_paths."""
dirs: list[str] = list()
if self.cur_dir != self.top_dir:
dirs.append(self.cur_dir)
for c_dir in os.listdir(self.cur_dir):
if os.path.isdir(os.path.join(self.cur_dir,
c_dir)) and c_dir != ".git":
dirs.append(os.path.join(self.cur_dir, c_dir))
dirs = sorted(dirs)
return dirs
def move_up(self) -> bool:
"""move_up."""
if self.cur_dir == self.top_dir:
return True
if not os.path.isdir(self.cur_dir):
self.cur_dir = os.path.abspath(os.path.join(self.cur_dir, os.pardir))
self.move_up()
return True
def save_to_pass(self, password, path, full_path):
command1: list[str] = ['/bin/echo', password.rstrip("\n")]
command2: list[str] = ['/usr/bin/pass', 'insert', '-m', path]
run_command(command1, command2)
self.cur_paths = sorted([full_path] + self.cur_paths)
self.cur_passwords = self.get_pass_passwords()
def copy_to_clipboard(text) -> tuple[Union[str, bytes], Union[str, bytes]]:
"""copy_to_clipboard.
:param text:
"""
password: str = text.split('\n')[0]
command1: list[str] = ['/bin/echo', password]
command2: list[str] = ['/usr/bin/wl-copy']
result: tuple[Union[str, bytes], Union[str, bytes]] = run_command(command1, command2)
return result
def get_password_from_path(pass_path) -> str:
"""get_password_from_path.
:param pass_path:
"""
result: tuple[Union[str, bytes], Union[str, bytes]] = run_command(['/usr/bin/pass', 'show', pass_path])
password: str = result[0].decode()
return password
def pass_pull():
"""pass_pull.
"""
run_command(['/usr/bin/pass', 'git', 'pull'])
def pass_push():
"""pass_push.
"""
run_command(['/usr/bin/pass', 'git', 'push'])
def run_command(command1, command2=None) -> tuple[Union[str, bytes], Union[str, bytes]]:
"""Run a command on system and capture result
:param command1:
:param command2:
"""
process1: subprocess.Popen = subprocess.Popen(command1,
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# If there is a second command it is taken to be a pipeline
if command2:
subprocess.Popen(command2,
shell=False,
stdin=process1.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
process1.stdout.close()
return process1.communicate()
Loading…
Cancel
Save