From 3acafaea32d362c27a6674b532e13941b48316bc4c41a25a11c597d6028a3777 Mon Sep 17 00:00:00 2001 From: linuxero Date: Mon, 9 Dec 2024 23:23:11 +0100 Subject: [PATCH] Some code organisation and recovery procedure fix --- Gui/AdminUserConfig.qml | 4 +- Gui/LoginScreen.qml | 54 +++++++++ Gui/firststart.qml | 237 +++++++++++++++++++--------------------- Gui/main.qml | 15 +-- lib/ConfigLoader.py | 190 +++++++++++++++++++------------- lib/DB/DbManager.py | 2 +- main.py | 37 +++---- 7 files changed, 305 insertions(+), 234 deletions(-) diff --git a/Gui/AdminUserConfig.qml b/Gui/AdminUserConfig.qml index 6863a15..b90212f 100644 --- a/Gui/AdminUserConfig.qml +++ b/Gui/AdminUserConfig.qml @@ -75,10 +75,10 @@ GridLayout Component.onCompleted: { - config.usernameNotAvailable.connect(usernameNotAvailable) + config.adminNotAvailable.connect(adminNotAvailable) } - function usernameNotAvailable() + function adminNotAvailable() { benutzerName.placeholderText = qsTr ("Benutzername ist bereits vergeben") benutzerName.clear() diff --git a/Gui/LoginScreen.qml b/Gui/LoginScreen.qml index a55c36c..e414d2c 100644 --- a/Gui/LoginScreen.qml +++ b/Gui/LoginScreen.qml @@ -1,10 +1,13 @@ +import QtCore import QtQuick import QtQuick.Controls +import QtQuick.Dialogs import QtQuick.Layouts Item { + property string recpass: "" anchors.fill: parent ColumnLayout @@ -133,11 +136,58 @@ Item Layout.fillHeight: true } + + Dialog + { + id: recoveryPaswordDialog + modal: true + title: qsTr("Wiederherstellung") + anchors.centerIn: parent + standardButtons: Dialog.Ok | Dialog.Cancel + onAccepted: + { + recpass = recoveryPaswordInput.text + getRecoveryDialog.open() + } + + ColumnLayout + { + RowLayout + { + Label + { + text: qsTr("Wiederherstellungspasswort eingeben: ") + } + + TextField + { + id: recoveryPaswordInput + text: "" + echoMode: TextInput.Password + implicitWidth: 300 + placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben") + } + } + } + } + + FileDialog + { + id: getRecoveryDialog + title: qsTr("Wiederherstellungsdatei") + fileMode: FileDialog.OpenFile + nameFilters: ["PYQCRM Recovery files (*.pyqrec)"] + currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] + onAccepted: config.getRecoveryKey(getRecoveryDialog.currentFile, recpass) + onRejected: quit() + } } Component.onCompleted: { loggedin_user.loginOkay.connect(loggedin) + config.invalidEncryptionKey.connect(getEncryptionKey) + config.checkEncryptionKey() } function loggedin() @@ -145,4 +195,8 @@ Item appLoader.source = "Dashboard.qml" } + function getEncryptionKey() + { + recoveryPaswordDialog.open() + } } diff --git a/Gui/firststart.qml b/Gui/firststart.qml index 579c52e..38544a8 100644 --- a/Gui/firststart.qml +++ b/Gui/firststart.qml @@ -11,113 +11,6 @@ Item { property string recpass: "" property bool adminAvailable: true - Component.onCompleted: - { - config.dbConnectionError.connect(onDbConnectionError) - config.adminUserError.connect(onAdminUserError) - config.backupEncryptionKey.connect(onBackupEncryptionKey) - - } - - function onBackupEncryptionKey() - { - recoveryDialog.open() - } - - function onDbConnectionError(msg, success) - { - if (!success) - conErrDialog.open() - } - - function onAdminUserError(msg, success) - { - if (success) - { - encryptPwDialog.open() - } - else - { - adminAvailable = false - firstStart.push("AdminUserConfig.qml") - } - - } - MessageDialog - { - id: recoveryDialog - - text: qsTr("Diesen Wiederherstellungscode musst du sicher aufbewahren!\nMöchtest du das jetzt machen?") - title: qsTr("Wiederherstellen") - buttons: MessageDialog.Yes | MessageDialog.No - onAccepted: saveRecoveryDialog.open() - - } - FileDialog - { - id: saveRecoveryDialog - title: qsTr("Wiederherstellungsdatei") - fileMode: adminAvailable? FileDialog.OpenFile: FileDialog.SaveFile - nameFilters: ["PYQCRM Recovery files (*.pyqrec)"] - currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] - onAccepted: - { - if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass) - else - { - config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass) - - } - appLoader.source = "Dashboard.qml" - topBar.visible = true - } - - } - - - MessageDialog - { - id: conErrDialog - text: qsTr("Datenbankverbindung fehlgeschlagen") - - title: qsTr("Datenbank Verbindung") - - - } - Dialog - { - id: encryptPwDialog - modal: true - title: qsTr("Wiederherstellung") - anchors.centerIn: parent - standardButtons: Dialog.Ok | Dialog.Cancel - onAccepted: - { - recpass = encryptPassword.text - saveRecoveryDialog.open() - - } - ColumnLayout - { - RowLayout - { - Label - { - text: qsTr("Wiederherstellungspasswort eingeben: ") - } - - TextField - { - id: encryptPassword - echoMode: TextInput.Password - implicitWidth: 300 - placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben") - } - } - } - } - - anchors.fill: parent StackView @@ -125,15 +18,14 @@ Item id: firstStart anchors.fill: parent initialItem: "DbConfiguration.qml" - //initialItem: "AdminUserConfig.qml" } + RowLayout { anchors.bottom: parent.bottom anchors.margins: 9 width: parent.width - Item { Layout.fillWidth: true @@ -164,28 +56,125 @@ Item if (pyqcrm_conf) { config.setConfig(pyqcrm_conf) - } } else { pyqcrm_conf = Qmldict.firstConf(submitBtn.grids) - if (pyqcrm_conf) - { - admin = config.addAdminUser(pyqcrm_conf) - if (admin) - { - //appLoader.source = "Dashboard.qml" - //topBar.visible = true - } - else - { - console.log("Konfiguration Admin fehlgeschlagen") - } - } - + if (pyqcrm_conf) config.addAdminUser(pyqcrm_conf) } } } } + + MessageDialog + { + id: recoveryDialog + + text: qsTr("Diesen Wiederherstellungscode musst du sicher aufbewahren!\nMöchtest du das jetzt machen?") + title: qsTr("Wiederherstellen") + buttons: MessageDialog.Yes | MessageDialog.No + onAccepted: recoveryPaswordDialog.open() + onRejected: gotoLogin() + } + + MessageDialog + { + id: conErrDialog + text: qsTr("Datenbankverbindung fehlgeschlagen") + title: qsTr("Datenbank Verbindung") + } + + Dialog + { + id: recoveryPaswordDialog + modal: true + title: qsTr("Wiederherstellung") + anchors.centerIn: parent + standardButtons: Dialog.Ok | Dialog.Cancel + onAccepted: + { + recpass = recoveryPaswordInput.text + saveRecoveryDialog.open() + } + + ColumnLayout + { + RowLayout + { + Label + { + text: qsTr("Wiederherstellungspasswort eingeben: ") + } + + TextField + { + id: recoveryPaswordInput + text: "" + echoMode: TextInput.Password + implicitWidth: 300 + placeholderText: qsTr("Hier Wiederherstellungspasswort eingeben") + } + } + } + } + + FileDialog + { + id: saveRecoveryDialog + title: qsTr("Wiederherstellungsdatei") + fileMode: adminAvailable? FileDialog.OpenFile: FileDialog.SaveFile + nameFilters: ["PYQCRM Recovery files (*.pyqrec)"] + currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] + onAccepted: + { + if (!adminAvailable) config.saveRecoveryKey(saveRecoveryDialog.currentFile, recpass) + else config.getRecoveryKey(saveRecoveryDialog.currentFile, recpass) + + gotoLogin() + } + + onRejected: + { + if (adminAvailable) quit() + } + } + + Component.onCompleted: + { + config.dbConnectionError.connect(onDbConnectionError) + config.adminUserError.connect(onAdminUserError) + config.backupEncryptionKey.connect(onBackupEncryptionKey) + } + + function gotoLogin() + { + appLoader.source= "LoginScreen.qml" + topBar.visible = true + } + + function onBackupEncryptionKey() + { + recoveryDialog.open() + } + + function onDbConnectionError(msg, success) + { + if (!success) + conErrDialog.open() + } + + function onAdminUserError(msg, success) + { + if (success) + { + recoveryPaswordDialog.open() + } + else + { + adminAvailable = false + firstStart.push("AdminUserConfig.qml") + } + } + } diff --git a/Gui/main.qml b/Gui/main.qml index be261a8..342251c 100644 --- a/Gui/main.qml +++ b/Gui/main.qml @@ -47,6 +47,7 @@ ApplicationWindow property alias window: appWindow } + Component.onCompleted: { if(bad_config) @@ -54,8 +55,8 @@ ApplicationWindow importDialog.open() } else appLoader.source= "LoginScreen.qml" - } + Dialog { id: importDialog @@ -65,11 +66,10 @@ ApplicationWindow onAccepted: settingsFiledialog.open() onRejected: appLoader.source= "firststart.qml" title: qsTr("Einstellungen importieren") - } + FileDialog { - id: settingsFiledialog title: qsTr("PYQCRM Einstellungen") currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] @@ -77,18 +77,19 @@ ApplicationWindow nameFilters: [qsTr("PYQCRM Einstellungen (*.pyqcrm)")] onAccepted: { - encryptPwDialog.open() + exportFilePassword.open() confile = selectedFile } } + Dialog { - id: encryptPwDialog + id: exportFilePassword modal: true title: qsTr("PYQCRM Einstellungen") anchors.centerIn: parent standardButtons: Dialog.Ok | Dialog.Cancel - onAccepted: config.importConfig(confile, encryptPassword.text) + onAccepted: config.importConfig(confile, exportPasswordInput.text) ColumnLayout { RowLayout @@ -100,7 +101,7 @@ ApplicationWindow TextField { - id: encryptPassword + id: exportPasswordInput echoMode: TextInput.Password implicitWidth: 300 } diff --git a/lib/ConfigLoader.py b/lib/ConfigLoader.py index 4922204..b1543a4 100644 --- a/lib/ConfigLoader.py +++ b/lib/ConfigLoader.py @@ -19,43 +19,120 @@ from .PyqcrmFlags import PyqcrmFlags class ConfigLoader(QObject): __config = None __version = "0.1-alpha" + __check_enc_key = True dbConnectionError = Signal(str, bool) adminUserError = Signal(str, bool) - usernameNotAvailable = Signal() - configurationReady = Signal(bool) + adminNotAvailable = Signal() + configurationReady = Signal() backupEncryptionKey = Signal() + invalidEncryptionKey = Signal() def __init__(self): super().__init__() - self.config_dir = user_config_dir() + '/pyqcrm' #user_config_dir = Funktion platformdirs + # print(f"In {__file__} file, __init__()") + self.config_dir = user_config_dir() + '/pyqcrm' config_dir = Path(self.config_dir) - if config_dir.exists(): self.__configLoad() + if self.__config: + self.checkEncryptionKey() else: config_dir.mkdir(0o750, True, True) + @Slot(dict, result= bool) + def setConfig(self, app_config): + # print(f"In {__file__} file, setConfig()") + if not self.__config: + base_conf = self.__initializeConfig() + conf = self.__checkDbConnection(app_config) + app_config = toml.dumps(app_config) + if conf: + app_config = base_conf + app_config + self.__config = toml.loads(app_config) + self.__saveConfig() + conf = self.__checkAdminUser() + if conf: + self.configurationReady.emit() + + def __initializeConfig(self): + # print(f"In {__file__} file, __initializeConfig()") + self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") + conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\n" + conf = conf + f"ENCRYPTION_KEY_VALID = \"No\"\n" + conf = conf + f"ENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n" + return conf + + def __checkDbConnection(self, db_config): + # print(f"In {__file__} file, __checkDbConnection()") + con = DbManager(db_config['database']).getConnection() + if con: + self.dbConnectionError.emit("Connection OK", True) + return True + else: + self.dbConnectionError.emit("Connection fehlgeschlagen", False) + return False + + + def __saveConfig(self): + # print(f"In {__file__} file, saveConfig()") + try: + with open (self.config_dir + '/pyqcrm.toml', 'w') as f: + # print(self.__config) + config = Vermasseln().oscarVermasseln(toml.dumps(self.__config)) + f.write(config) + except FileNotFoundError: + print("Konnte die Konfiguration nicht speichern.") + + + def __checkAdminUser(self): + # print(f"In {__file__} file, __checkAdminUser()") + con = DbManager().getConnection() + cur = con.cursor() + cur.callproc("checkAdmin") + result = cur.fetchall() + if not result: + #if not result[0][0] == 1: + self.adminUserError.emit("Kein Admin vorhanden", False) + return False + else: + self.adminUserError.emit("Admin vorhanden", True) + return True + + @Slot(dict, result= bool) + def addAdminUser(self, user_config): + # print(f"In {__file__} file, addAdminUser()") + admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser() + if not admin: + #self.adminNotAvailable.emit() + self.adminUserError.emit("Benutzername nich verfügbar", False) + else: + self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'Yes' + self.__saveConfig() + self.backupEncryptionKey.emit() + return admin + @Slot(str, str) def saveRecoveryKey(self, recovery_file, recovery_password): + # print(f"In {__file__} file, saveRecoveryKey()") rp = self.__setRecoveryPassword(recovery_password) rf = rp[1] + self.__encrypt_key + rp[0] rf = Vermasseln().oscarVermasseln(rf) rec_file = urlparse(recovery_file) - rec_file = rec_file.path + rec_file = rec_file.path + ".pyqrec" if os.name == "nt": rec_file = rec_file [1:] try: with open(rec_file, "w") as f: f.write(rf) - self.configurationReady.emit(True) + self.configurationReady.emit() except Exception as e: print(str(e)) - @Slot(str, str) def getRecoveryKey(self, recovery_file, recovery_password): + # print(f"In {__file__} file, getRecoveryKey()") rec_file = urlparse(recovery_file) rec_file = rec_file.path if os.name == "nt": @@ -70,21 +147,33 @@ class ConfigLoader(QObject): salt = rf[-32:] ok = self.__checkRecoveryPassword(recovery_password, password, salt) if ok: - self.configurationReady.emit(True) + self.__setEncryptionKey(ek) + self.configurationReady.emit() else: - self.configurationReady.emit(False) + self.__invalidateEncryptionKey() + self.invalidEncryptionKey.emit() except Exception as e: print(str(e)) + def __invalidateEncryptionKey(self): + # print(f"In {__file__} file, __invalidateEncryptionKey()") + self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'No' + self.__saveConfig() + + @Slot() + def checkEncryptionKey(self): + # print(f"In {__file__} file, __checkEncryptionKey()") + if self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] == 'No': + self.invalidEncryptionKey.emit() + def __checkRecoveryPassword(self, recovery_password, password, salt): + # print(f"In {__file__} file, __checkRecoveryPassword()") rp = self.__setRecoveryPassword(recovery_password, salt) return rp[1] == password - - @Slot(str, str) def importConfig(self, confile, password): - + # print(f"In {__file__} file, importConfig()") confile = urlparse(confile) confile = confile.path @@ -92,38 +181,13 @@ class ConfigLoader(QObject): confile = confile[1:] shutil.copyfile(confile, self.config_dir+ '/pyqcrm.toml') - @Slot(dict, result= bool) - def addAdminUser(self, user_config): - admin = UserManager(user_config["user"], PyqcrmFlags.ADMIN).createUser() - - if not admin: - self.usernameNotAvailable.emit() - else: - self.backupEncryptionKey.emit() - return admin - - @Slot(dict, result= bool) - def setConfig(self, app_config): - base_conf = self.__initializeConfig() - conf = self.__checkDbConnection(app_config) - app_config = toml.dumps(app_config) - - if conf: - app_config = base_conf + app_config - self.__config = toml.loads(app_config) - self.__saveConfig() - conf = self.__checkAdminUser() - if conf: - self.configurationReady.emit(True) - - - def __configLoad(self): + # print(f"In {__file__} file, __configLoad()") try: with open (self.config_dir + '/pyqcrm.toml', 'r') as f: config = f.read() self.__config = toml.loads(Vermasseln().entschluesseln(config)) - self.configurationReady.emit(True) + self.configurationReady.emit() except FileNotFoundError: print("Konnte die Konfiguration nicht laden.") except TypeError: @@ -131,50 +195,20 @@ class ConfigLoader(QObject): except Exception as e: print(str(e)) - def getConfig(self): + # print(f"In {__file__} file, getConfig()") + # print(self.__config) return self.__config - def __initializeConfig(self): - self.__encrypt_key = b64encode(get_random_bytes(32)).decode("utf-8") - conf = f"[pyqcrm]\nVERSION = \"{self.__version}\"\nENCRYPTION_KEY = \"{self.__encrypt_key}\"\n\n" - return conf - - - def __checkDbConnection(self, db_config): - con = DbManager(db_config['database']).getConnection() - if con: - self.dbConnectionError.emit("Connection OK", True) - return True - else: - self.dbConnectionError.emit("Connection fehlgeschlagen", False) - return False - - def __checkAdminUser(self): - con = DbManager().getConnection() - cur = con.cursor() - cur.callproc("checkAdmin") - result = cur.fetchall() - if not result: - #if not result[0][0] == 1: - self.adminUserError.emit("Kein Admin vorhanden", False) - return False - else: - self.adminUserError.emit("Admin vorhanden", True) - return True - - def __setRecoveryPassword(self, key, salt = None): + # print(f"In {__file__} file, __setRecoveryPassword()") key = Vermasseln.userPasswordHash(key, salt) return key.split("$") - def __saveConfig(self): - - try: - with open (self.config_dir + '/pyqcrm.toml', 'w') as f: - config = Vermasseln().oscarVermasseln(toml.dumps(self.__config)) - f.write(config) - except FileNotFoundError: - print("Konnte die Konfiguration nicht speichern.") - + @Slot(str) + def __setEncryptionKey(self, enc_key): + # print(f"In {__file__} file, __setEncryptionKey()") + self.__config['pyqcrm']['ENCRYPTION_KEY_VALID'] = 'Yes' + self.__config['pyqcrm']['ENCRYPTION_KEY'] = enc_key + self.__saveConfig() diff --git a/lib/DB/DbManager.py b/lib/DB/DbManager.py index a9587f6..bb15208 100644 --- a/lib/DB/DbManager.py +++ b/lib/DB/DbManager.py @@ -21,7 +21,7 @@ class DbManager(object): cls.__connection = mariadb.connect(**cls.__con_param) except mariadb.InterfaceError as e: cls.__connection = mariadb.connect(**cls.__con_param) - print(f"INTERFACE ERROR: {e}") + print(f"DbManager Connection (INTERFACE ERROR): {e}..reconnecting...") except mariadb.Error as e: print(f"Connection parameters are wrong: {e}") diff --git a/main.py b/main.py index 6826d29..f8079eb 100644 --- a/main.py +++ b/main.py @@ -26,32 +26,31 @@ from lib.DB.AddressModel import AddressModel # name="" # type="" +bad_config = False am = None bm = None user = None -def initializeProgram(conf_ok): - if conf_ok: - global am, bad_config, bm, user +def initializeProgram(): + # print(f"In {__file__} file, initializeProgram()") + global am, bad_config, bm, user + if not bad_config: dbconf = config.getConfig()['database'] DbManager(dbconf) - bad_config = False - bm = BusinessModel() - user = UserManager() - am = AddressModel() - publishContext() - else: - # TODO: show message and exit program - print("Kein Zugriff auf die Datenbank") + bad_config = False + bm = BusinessModel() + user = UserManager() + am = AddressModel() + + publishContext() def publishContext(): - global engine + # print(f"In {__file__} file, publishContext()") + global engine, am, bad_config, bm, user engine.rootContext().setContextProperty("loggedin_user", user) engine.rootContext().setContextProperty("bm", bm) engine.rootContext().setContextProperty("am", am) - - if __name__ == "__main__": #QResource.registerResource("rc_qml.py") app = QGuiApplication(sys.argv) @@ -59,12 +58,6 @@ if __name__ == "__main__": engine.addImportPath("qrc:/"); - - - bad_config = False - - - # qml_file = Path(__file__).resolve().parent / "Gui/main.qml" qml_file = "qrc:/Gui/main.qml" @@ -72,16 +65,16 @@ if __name__ == "__main__": config = ConfigLoader() - if not config.getConfig(): bad_config = True config.configurationReady.connect(initializeProgram) - else: initializeProgram() + engine.rootContext().setContextProperty("bad_config", bad_config) # print(f"Fehler: {i}") engine.rootContext().setContextProperty("config", config) + engine.load(qml_file)