SamAuthenticator/SamAuthenticator/AuthenticatorWindow.py

247 lines
10 KiB
Python

from PyQt5.QtWidgets import QMainWindow, QWidget, QGridLayout, QLineEdit, QAction, qApp, QInputDialog, \
QMessageBox, QFileDialog
from PyQt5.QtCore import QSortFilterProxyModel, pyqtSlot, QSettings
from PyQt5.Qt import Qt, QIcon, QSizePolicy
import SamAuthenticator.KeysDataModel as model
import SamAuthenticator.KeyDataView as dataview
import SamAuthenticator.Authenticator as auth
import SamAuthenticator.TrayIcon as tray
import functools
import os
class AuthenticatorGUI(QMainWindow):
new_key_to_add_slot = pyqtSlot(str, str)
def __init__(self):
super().__init__()
self.data_file_name = "data.dat"
self.load_geometry()
current_path = os.path.dirname(os.path.abspath(__file__))
main_icon = QIcon(os.path.join(current_path, "images/key.png"))
# self.setWindowIcon(main_icon)
self.tray_icon = tray.SamAuthenticatorTrayIcon(self, main_icon)
self.tray_icon.show()
self.mainWidget = QWidget()
self.setCentralWidget(self.mainWidget)
self.main_layout = QGridLayout()
self.filter_line_edit = QLineEdit()
self.filter_line_edit.setPlaceholderText("Enter filter (Ctrl+F)")
self.keys_table_view = dataview.KeyDataView()
self.keys_data_model = None
self.keys_data_model_proxy = None
self.setup_data_model(auth.AuthenticatorKeys())
self.main_layout.addWidget(self.filter_line_edit, 1, 0, 1, 2)
self.main_layout.addWidget(self.keys_table_view, 2, 0, 1, 2)
self.mainWidget.setLayout(self.main_layout)
self.filter_line_edit.textChanged.connect(self.set_filter_string)
self.add_menus()
self.add_toolbar()
self.setWindowTitle('Sam Authenticator')
self.show()
self.load_data_from_default_path()
def set_filter_string(self, filter_str):
if self.keys_data_model_proxy is not None:
self.keys_data_model_proxy.setFilterWildcard(filter_str)
def set_data_file_name(self, file_name):
self.data_file_name = file_name
def import_data(self):
file_name = QFileDialog.getOpenFileName(self, "Import data from...", "", "Encrypted data (*.dat, *.*)")
# if a file is chosen
if file_name[1]:
if os.path.exists(file_name[0]):
self.load_data_from(file_name[0])
else:
QMessageBox.warning(self, "File not found", "The path you chose doesn't contain a file.")
def load_data_from_default_path(self):
if not os.path.exists(self.data_file_name):
return
self.load_data_from(self.data_file_name)
self.filter_line_edit.clear()
def decrypt_data_file(self):
source_file = QFileDialog.getOpenFileName(self, "Choose the file to decrypt...", "", "Encrypted data (*.dat, *.*)")
# if a file is chosen
if not source_file[1]:
return
if not os.path.exists(source_file[0]):
QMessageBox.warning(self, "File not found", "The path you chose doesn't contain a file.")
return
dest_file = QFileDialog.getSaveFileName(self, "Save decrypted file as...", "", "JSON file (.json)")
# if a file is chosen
if not dest_file[1]:
return
password_from_dialog = QInputDialog.getText(self, "Input encryption password",
"Encryption password:",
QLineEdit.Password, "")
ok_pressed = password_from_dialog[1]
if not ok_pressed:
return
try:
with open(source_file[0], 'rb') as f_load:
ciphered_data = f_load.read()
readable_data = auth.decrypt_data(ciphered_data, password_from_dialog[0], auth.get_default_salt())
with open(dest_file[0], 'wb') as f_save:
f_save.write(readable_data)
except Exception as e:
QMessageBox.warning(self, "Error", "Decryption failed. " + str(e))
def load_data_from(self, data_file_path):
password_from_dialog = QInputDialog.getText(self, "Input encryption password",
"Encryption password:",
QLineEdit.Password, "")
ok_pressed = password_from_dialog[1]
if not ok_pressed:
return
try:
keys = auth.read_keys_from_file(password_from_dialog[0], data_file_path)
self.setup_data_model(keys)
except Exception as e:
QMessageBox.warning(self, "Unable to read data", "Unable to read data. " + str(e))
def save_data(self, data_file):
pass_from_dialog_1 = QInputDialog.getText(self, "Input encryption password",
"New encryption password:",
QLineEdit.Password, "")
ok_pressed_1 = pass_from_dialog_1[1]
if not ok_pressed_1:
return
pass_from_dialog_2 = QInputDialog.getText(self, "Repeat encryption password",
"Repeat encryption password:",
QLineEdit.Password, "")
ok_pressed_2 = pass_from_dialog_2[1]
if not ok_pressed_2:
return
if pass_from_dialog_1[0] != pass_from_dialog_2[0]:
QMessageBox.warning(self, "Error", "Password mismatch. Please try again")
self.save_data(data_file)
return
password = pass_from_dialog_1[0]
if self.keys_data_model is not None:
auth.write_keys_to_file(self.keys_data_model.getKeysObject(), password, data_file)
else:
QMessageBox.warning(self, "No data loaded", "Data should be loaded before attempting to save it")
def save_data_as(self):
file_name = QFileDialog.getSaveFileName(self, "Save data as...", "", "Encrypted data (*.dat)")
# if a file is chosen
if file_name[1]:
self.save_data(file_name[0])
def add_menus(self):
exit_act = QAction('&Exit', self)
exit_act.setShortcut('Ctrl+Q')
exit_act.setStatusTip('Exit application')
exit_act.triggered.connect(qApp.quit)
reload_data_act = QAction('&Reload data from default location (' + self.data_file_name + ')' , self)
reload_data_act.setStatusTip('Reload data from file')
reload_data_act.setShortcut('Ctrl+R')
reload_data_act.triggered.connect(self.load_data_from_default_path)
save_data_act = QAction('&Save data to default location (' + self.data_file_name + ')', self)
save_data_act.setShortcut('Ctrl+S')
save_data_act.setStatusTip('Save data to the default path')
save_data_act.triggered.connect(functools.partial(self.save_data, self.data_file_name))
save_data_as_act = QAction('&Save data as...', self)
save_data_as_act.setShortcut('Ctrl+Shift+S')
save_data_as_act.setStatusTip('Save data to...')
save_data_as_act.triggered.connect(self.save_data_as)
import_data_act = QAction('&Import data from...' , self)
import_data_act.setStatusTip('Import data from a file...')
import_data_act.triggered.connect(self.import_data)
decrypt_data_file_act = QAction('&Decrypt a data file', self)
decrypt_data_file_act.setStatusTip('Decrypt a data file to raw text (unsafe)')
decrypt_data_file_act.triggered.connect(self.decrypt_data_file)
QAction("File")
file_menu = self.menuBar().addMenu('&File')
file_menu.addAction(reload_data_act)
file_menu.addAction(save_data_act)
file_menu.addSeparator()
file_menu.addAction(import_data_act)
file_menu.addAction(save_data_as_act)
file_menu.addSeparator()
file_menu.addAction(decrypt_data_file_act)
file_menu.addSeparator()
file_menu.addAction(exit_act)
def add_toolbar(self):
toolbar = self.addToolBar("File")
left_spacer = QWidget()
left_spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
right_spacer = QWidget()
right_spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
current_path = os.path.dirname(os.path.abspath(__file__))
toolbar.addWidget(left_spacer)
add_act = QAction(QIcon(os.path.join(current_path, "images/add.png")), "Add new key (Ctrl+N)", self)
add_act.setShortcut('Ctrl+N')
toolbar.addAction(add_act)
remove_act = QAction(QIcon(os.path.join(current_path, "images/delete.png")), "Remove selected key", self)
remove_act.setShortcut('Ctrl+D')
toolbar.addAction(remove_act)
toolbar.addWidget(right_spacer)
add_act.triggered.connect(self.keys_table_view.add_new_key_from_dialog)
remove_act.triggered.connect(self.keys_table_view.remove_row)
def setup_data_model(self, keys):
self.keys_data_model_proxy = QSortFilterProxyModel()
self.keys_data_model_proxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
self.keys_data_model = model.AuthenticatorKeysDataModel(keys)
self.keys_table_view.set_real_data_model(self.keys_data_model)
self.keys_data_model_proxy.setSourceModel(self.keys_data_model)
self.keys_table_view.setModel(self.keys_data_model_proxy)
def keyPressEvent(self, event):
if event.key() == Qt.Key_C and event.modifiers() & Qt.ControlModifier:
self.keys_table_view.copy_selected()
if event.key() == Qt.Key_F and event.modifiers() & Qt.ControlModifier:
self.filter_line_edit.setFocus()
self.filter_line_edit.selectAll()
if event.key() == Qt.Key_Escape:
self.filter_line_edit.clear()
def load_geometry(self):
settings = QSettings("SamApps", "SamAuthenticator")
geometry_values = settings.value("geometry")
if geometry_values is not None:
self.restoreGeometry(geometry_values)
def closeEvent(self, event):
settings = QSettings("SamApps", "SamAuthenticator")
settings.setValue("geometry", self.saveGeometry())
super().closeEvent(event)