From 49473c85bfa283b590d70cd9811ef070126b8394 Mon Sep 17 00:00:00 2001 From: Micke Nordin Date: Thu, 10 Jun 2021 09:14:26 +0200 Subject: [PATCH] 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. --- src/{passui => main.py} | 166 +++--------------- src/pass_handler/__init__.py | 132 ++++++++++++++ .../__pycache__/__init__.cpython-39.pyc | Bin 0 -> 3119 bytes 3 files changed, 159 insertions(+), 139 deletions(-) rename src/{passui => main.py} (69%) create mode 100644 src/pass_handler/__init__.py create mode 100644 src/pass_handler/__pycache__/__init__.cpython-39.pyc diff --git a/src/passui b/src/main.py similarity index 69% rename from src/passui rename to src/main.py index 421139f..5581f28 100755 --- a/src/passui +++ b/src/main.py @@ -1,18 +1,20 @@ -#!/usr/bin/env python3 +#!/usr/bin/python3 """ A mobile first interface for the standard unix password manager written in python """ import os -import subprocess import wx 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): """ The wx.Frame for passui """ + def __init__(self, *args, **kw): """__init__. @@ -20,33 +22,27 @@ class PassUi(wx.Frame): :param kw: """ super().__init__(*args, **kw) - # Find the top directory and set that to current directory - self.topdir = os.environ.get('PASSWORD_STORE_DIR') - if not self.topdir: - self.topdir = os.environ.get('HOME') + '/.password-store' - self.curdir = self.topdir + self.pass_handler = Pass() # create a panel in the frame self.pnl = scrolled.ScrolledPanel(self, -1, style=wx.VSCROLL) self.pnl.SetupScrolling() # and create a sizer to manage the layout of child widgets self.sizer = wx.BoxSizer(wx.VERTICAL) self.pnl.SetSizer(self.sizer) - self.cur_paths = self.get_pass_paths() - self.cur_passwords = self.get_pass_passwords() self.add_buttons() def add_buttons(self): """add_buttons.""" self.sizer.Clear(delete_windows=True) self.add_tools() - if self.curdir != self.topdir: + if self.pass_handler.cur_dir != self.pass_handler.top_dir: btn = self.make_back_button() self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member self.Bind(wx.EVT_BUTTON, lambda event: self.path_button_clicked(), btn) index = 0 - for cpath in self.cur_paths: - if cpath != self.curdir: + for cpath in self.pass_handler.cur_paths: + if cpath != self.pass_handler.cur_dir: label = '๐Ÿ—€ ' + os.path.basename(os.path.normpath(cpath)) btn = wx.Button(self.pnl, label=label) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member @@ -56,7 +52,7 @@ class PassUi(wx.Frame): btn) index = index + 1 index = 0 - for password in self.cur_passwords: + for password in self.pass_handler.cur_passwords: label = '๐Ÿ— ' + os.path.splitext( os.path.basename(os.path.normpath(password)))[0] btn = wx.Button(self.pnl, label=label) @@ -106,7 +102,7 @@ class PassUi(wx.Frame): :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, "Delete " + path + "?", "Are you sure?", @@ -115,69 +111,23 @@ class PassUi(wx.Frame): reply = dlg.ShowModal() if reply == wx.ID_CANCEL: return - command1 = ['/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() + self.pass_handler.delete_password(path) 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): """make_back_button. :param index: """ 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: - 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 + 'โคด') font = btn.GetFont().MakeItalic().MakeBold() btn.SetFont(font) 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): """password_button_clicked. @@ -191,10 +141,10 @@ class PassUi(wx.Frame): :param path: """ if path is None: - path = os.path.abspath(os.path.join(self.curdir, os.pardir)) - self.curdir = path - self.cur_paths = self.get_pass_paths() - self.cur_passwords = self.get_pass_passwords() + path = os.path.abspath(os.path.join(self.pass_handler.cur_dir, os.pardir)) + self.pass_handler.cur_dir = path + self.pass_handler.cur_paths = self.pass_handler.get_pass_paths() + self.pass_handler.cur_passwords = self.pass_handler.get_pass_passwords() self.add_buttons() def save_to_pass(self, path, text, name=None): @@ -204,10 +154,10 @@ class PassUi(wx.Frame): :param text: :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: 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' if os.path.exists(fullpath) or os.path.exists(filename): dlg = wx.MessageDialog( @@ -222,8 +172,7 @@ class PassUi(wx.Frame): dlg = wx.MessageDialog(self.pnl, "Save to " + path + "?", "Are you sure?", - style=wx.CANCEL | wx.CANCEL_DEFAULT - | wx.OK) + style=wx.CANCEL | wx.CANCEL_DEFAULT | wx.OK) dlg.SetOKCancelLabels("&Yes", "&Don't save") reply = dlg.ShowModal() if reply == wx.ID_CANCEL: @@ -233,11 +182,7 @@ class PassUi(wx.Frame): password += text.GetLineText(lineno) password += '\n' - command1 = ['/bin/echo', password.rstrip("\n")] - 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.pass_handler.save_to_pass(password,path,fullpath) self.back_button_clicked() def show_new_dialog(self): @@ -245,11 +190,11 @@ class PassUi(wx.Frame): """ 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() self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member 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), btn) @@ -282,8 +227,8 @@ class PassUi(wx.Frame): self.sizer.Clear(delete_windows=True) self.add_tools(index) - passpath = self.get_pass_path_from_index(index, "password") - cpath = self.topdir + os.path.dirname(passpath) + passpath = self.pass_handler.get_pass_path_from_index(index, "password") + cpath = self.pass_handler.top_dir + os.path.dirname(passpath) password = get_password_from_path(passpath) btn = self.make_back_button(index) @@ -320,8 +265,8 @@ class PassUi(wx.Frame): """ self.sizer.Clear(delete_windows=True) self.add_tools(index) - passpath = self.get_pass_path_from_index(index, "password") - cpath = self.topdir + os.path.dirname(passpath) + passpath = self.pass_handler.get_pass_path_from_index(index, "password") + cpath = self.pass_handler.top_dir + os.path.dirname(passpath) password = get_password_from_path(passpath) btn = self.make_back_button(index) self.sizer.Add(btn, 0, wx.EXPAND) # pylint: disable=no-member @@ -373,63 +318,6 @@ class PassUi(wx.Frame): 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__': # When this module is run (not imported) then create the app, the # frame, show it, and start the event loop. diff --git a/src/pass_handler/__init__.py b/src/pass_handler/__init__.py new file mode 100644 index 0000000..2753a01 --- /dev/null +++ b/src/pass_handler/__init__.py @@ -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() diff --git a/src/pass_handler/__pycache__/__init__.cpython-39.pyc b/src/pass_handler/__pycache__/__init__.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..566f63d71782f0b1bf1969fd8079f7d5519495ea GIT binary patch literal 3119 zcmbVO-EJF26rR~XZyYCeQ=l!h;EM{?h}twz3I$bATA&i7QBtXrC0LxDZM^B)yPa7# zjpbamw|xNa9PauAT=N=p%N4Isfy#Gg*Qp%{7wl^O&itM8&3DdBcWJ50@csPjZ$EWv zjQviH+0RDf3ncX&GQk8-Sf?m?hkwI_CG4k6*iWpEB^=>CWgT01qJq*9zF0u%imF&d z>4{}g6H8Bdry}?!Yo7Q6E3&na^4afj$B!Y6SceOSwYjjchAo!NE*z^DySU{pUb#y} z)UfulcGkN}g?wgVs?*wEA*rvCPx&?Vay$jq2@I21RyLDK4ozl~Hp+ zTdC5OXz(bO=|J1tQLeo_%|tA<7Y-$r%Mb=M4_V$*dI9pXx-!#MtgG}=(Oov5bM2@o zx&NHW6R`5d)pjrKN9}$bK8V^X9m+6LZCZR7x0MV{HRyE*B8g->2;xDU2SF>_)Bdzk zA;drq#`rS#_zL%Vm5-JP;nsAFc1YEb0G@shDwGW1AE7b^*bglE8lYxlewOXqgDUF0 zsE_T5MTa1)0}IDuN`iN^F=KMx3+~Ib4=qLXxK*t-%I{j%mEC^Bh_4-$l4ki{7QO1w z{`6Edd*QUUv+brM$wEoqper)Ul5QADvK&3jBy`Q<9n36N)1HjfFv$(dMOLSAXO2^s zPDA;~nCJ7F1qja@2#*^-$6f-Kv@u}bKxLcl^PEk<-5s#?Ci~&SzBRTcHt|{3$9&>| z%kBhh-GTf7V>o+Q&nsj8gzp1#Kx>Uo&Fo#$rzKy<>P=tL(dkMOs~nV+q__5?fl#|~ z-qT(;%g~T3BzAVvc%VI%$~+QH+rZUXm?Eq?ZvTD^a*_S7aFUB?zJ*jU%W`-ftU3d3 zjZPnhrBv;BKr*aDfWTO|=nj8i%X5H$_{7NY+@8RJsScp7?OVA`(3rep<`{tFMZ-`V zCFUOO1$-BCe-6GAGw{JrTO&{@&YKGcEkho8j*KQK<(rflyxdq3q~}TSHnI{j?Lexu z6H@09uof`v0+Kp|i~+0~{yKi^rN_OOmSuoDbrd)#H3u5SKo7~Vh+?2Io&d9CouYDX z?Hod-ZkU#9a^ey7R)|T6f|xn+4;aUdV3_3h(??M-%vyKn%r};k(efQ+y3*ZJa`$vIP4}xk32~`ou!OuC<$IkJI(BF^dOIx7BUw}QE>1r->=sE1;1wGbMVq2a#REF zd#C^;0EElLLbSt`GUnUXJ}$~_zHjGzY>jbo?mJ@#5W1sgm}YxHo(5qOXIm+-DFa*? z-16vgUW72stCEg#w9wv)2kj{ArMPK^N}A?wa*0-ITG~}v66bnR4hKP)_WKC(O;TTIm)9w}D0=qz P+_#sn_%*-kSNzJ~*!{=P literal 0 HcmV?d00001