From ebfc6f500df4f30c5e4b741efe9630e4e016fbcc Mon Sep 17 00:00:00 2001 From: Quentin Rouland Date: Mon, 20 Jan 2025 14:46:47 +0100 Subject: [PATCH] Add delete password store feature --- plugins/Git/jobs/clonejob.h | 12 +-- plugins/Git/jobs/gitjob.h | 6 +- plugins/Pass/CMakeLists.txt | 1 + plugins/Pass/gpg.cpp | 1 - plugins/Pass/jobs/rmjob.cpp | 24 +++++ plugins/Pass/jobs/rmjob.h | 46 ++++++++++ plugins/Pass/pass.cpp | 27 ++++++ plugins/Pass/pass.h | 40 ++++++-- po/utpass.qrouland.pot | 87 ++++++++++++------ qml/pages/PasswordList.qml | 32 ++++++- qml/pages/settings/DeleteRepo.qml | 92 +++++++++++++++++++ qml/pages/settings/InfoKeys.qml | 9 +- qml/pages/settings/Settings.qml | 5 + .../settings/git/GitModeOptionSelector.qml | 8 +- qml/pages/settings/git/ImportGitClone.qml | 4 +- 15 files changed, 329 insertions(+), 65 deletions(-) create mode 100644 plugins/Pass/jobs/rmjob.cpp create mode 100644 plugins/Pass/jobs/rmjob.h create mode 100644 qml/pages/settings/DeleteRepo.qml diff --git a/plugins/Git/jobs/clonejob.h b/plugins/Git/jobs/clonejob.h index afc9ff9..5fee646 100644 --- a/plugins/Git/jobs/clonejob.h +++ b/plugins/Git/jobs/clonejob.h @@ -11,8 +11,6 @@ extern "C" { * @class CloneJob * @brief A class to handle cloning Git repositories in a separate thread. * - * The CloneJob runs a cloning process in a separate thread and emits - * signals to indicate the success or failure of the operation. */ class CloneJob : public GitJob { @@ -21,9 +19,8 @@ class CloneJob : public GitJob /** * @brief The main function that performs the cloning operation. * - * This function overrides the `run()` method from the `QThread` class. It is - * executed when the thread is started, and it handles the process of cloning - * a repository from the specified URL to the target path. + * Handles the process of cloning a repository from the specified URL + * to the target path. */ void run() override; @@ -31,8 +28,7 @@ signals: /** * @brief Signal emitted when the cloning operation is complete. * - * This signal is emitted once the cloning operation finishes. It notifies - * the caller whether an error occurred during the cloning process. + * This signal is emitted once the cloning operation finishes. * * @param err A boolean indicating whether an error occurred during cloning. * `true` if an error occurred, `false` if the clone was successful. @@ -47,8 +43,6 @@ private: * @brief Prepares the temporary directory for cloning. * * This method sets up the required directory structure for cloning a repository. - * It is called before the cloning process begins to ensure that a suitable - * location exists for the repository to be cloned into. * * @return A `QDir` object representing the prepared temporary directory. */ diff --git a/plugins/Git/jobs/gitjob.h b/plugins/Git/jobs/gitjob.h index c33a407..46eb2a1 100644 --- a/plugins/Git/jobs/gitjob.h +++ b/plugins/Git/jobs/gitjob.h @@ -30,10 +30,8 @@ typedef std::variant cred_type; * @class GitJob * @brief A class that manages Git-related tasks using libgit2. * - * The GitJob class is used to perform Git operations, such as cloning repositories, - * in a separate thread. It utilizes libgit2 for interacting with Git repositories. - * The class handles credentials for repository access and allows the user to specify - * the type of credentials to use. + * The GitJob class is used abstraction class to perform Git operations, such as cloning repositories, + * in a separate thread using libgit2 for interacting with Git repositories. */ class GitJob : public QThread { diff --git a/plugins/Pass/CMakeLists.txt b/plugins/Pass/CMakeLists.txt index 9c2041a..5239f88 100644 --- a/plugins/Pass/CMakeLists.txt +++ b/plugins/Pass/CMakeLists.txt @@ -8,6 +8,7 @@ set( gpg.cpp passkeymodel.h passphraseprovider.h + jobs/rmjob.cpp ) set(CMAKE_AUTOMOC ON) diff --git a/plugins/Pass/gpg.cpp b/plugins/Pass/gpg.cpp index 1edbc6b..8f6c1fb 100644 --- a/plugins/Pass/gpg.cpp +++ b/plugins/Pass/gpg.cpp @@ -25,7 +25,6 @@ #include "gpg.h" -#include "passkeymodel.h" #include "passphraseprovider.h" diff --git a/plugins/Pass/jobs/rmjob.cpp b/plugins/Pass/jobs/rmjob.cpp new file mode 100644 index 0000000..30a0d61 --- /dev/null +++ b/plugins/Pass/jobs/rmjob.cpp @@ -0,0 +1,24 @@ +#include "rmjob.h" + +RmJob::RmJob(QString path): + m_path(path) +{ + this->setObjectName("RmJob"); +} + + +void RmJob::run() +{ + auto info = QFileInfo(this->m_path); + if (info.isFile()) { + auto file = QFile(this->m_path); + file.remove(); + emit resultReady(false); + } else if (info.isDir()) { + auto dir = QDir(this->m_path); + dir.removeRecursively(); + emit resultReady(false); + } else { + emit resultReady(true); + } +} diff --git a/plugins/Pass/jobs/rmjob.h b/plugins/Pass/jobs/rmjob.h new file mode 100644 index 0000000..18ba55b --- /dev/null +++ b/plugins/Pass/jobs/rmjob.h @@ -0,0 +1,46 @@ +#ifndef RMJOB_H +#define RMJOB_H + +#include +#include + +/** + * @class RmJob + * @brief A class to handle removing recursively a path in a separate thread. + * + */ +class RmJob : public QThread +{ + Q_OBJECT + + /** + * @brief The main function that performs the rm operation. + * + * Handles the process of removing recursively a target path. + */ + void run() override; + +signals: + /** + * @brief Signal emitted when the rm operation is complete. + * + * @param err A boolean indicating whether an error occurred during cloning. + * `true` if an error occurred, `false` if the clone was successful. + */ + void resultReady(const bool err); + +private: + QString m_path; ///< The path to be removed. + +public: + /** + * @brief Constructor for the RmJob class. + * + * Initializes the RmJob with the specified path to be removed. + * + * @param path Path to be remove. + */ + RmJob(QString path); +}; + +#endif // RMJOB_H diff --git a/plugins/Pass/pass.cpp b/plugins/Pass/pass.cpp index a159f13..a36081e 100644 --- a/plugins/Pass/pass.cpp +++ b/plugins/Pass/pass.cpp @@ -2,6 +2,7 @@ #include #include +#include "jobs/rmjob.h" #include "pass.h" #include "gpg.h" #include "passkeymodel.h" @@ -49,6 +50,7 @@ bool Pass::show(QUrl url) return this->m_gpg->decryptFromFile(path); } + void Pass::showResult(Error err, QString plain_text) { qDebug() << "Pass show Result"; @@ -67,6 +69,31 @@ void Pass::showResult(Error err, QString plain_text) this->m_sem->release(1); } +bool Pass::deletePasswordStore() +{ + qInfo() << "Pass delete Password Store"; + auto job = new RmJob(this->password_store()); + qDebug() << "Delete Password Store at " << this->password_store(); + connect(job, &RmJob::resultReady, this, &Pass::deletePasswordStoreResult); + connect(job, &RmJob::finished, job, &QObject::deleteLater); + job->start(); + return true; +} + +void Pass::deletePasswordStoreResult(bool err) +{ + qDebug() << "Pass delete Password StoreResult"; + if (err) { //dir.removeRecursively()) { + qInfo() << "Pass delete Password Store Failed"; + emit deletePasswordStoreFailed("failed to delete password store"); + + } else { + qInfo() << "Pass delete Password Store Succeed"; + emit deletePasswordStoreSucceed(); + } +} + + bool Pass::deleteGPGKey(PassKeyModel* key) { if (!this->m_sem->tryAcquire(1, 500)) { diff --git a/plugins/Pass/pass.h b/plugins/Pass/pass.h index 781d0c8..a2bd0dd 100644 --- a/plugins/Pass/pass.h +++ b/plugins/Pass/pass.h @@ -49,6 +49,12 @@ private slots: */ void getAllGPGKeysResult(Error err, std::vector keys_info); + /** + * @brief Slot to handle the result of a delete Password Store operation. + * @param err True if an error occurred during the operation. + */ + void deletePasswordStoreResult(bool err); + signals: // GPG-related signals /** @@ -111,6 +117,18 @@ signals: */ void showCancelled(); + + /** + * @brief Emitted when the password store is successfully deleted. + */ + void deletePasswordStoreSucceed(); + + /** + * @brief Emitted when deleting the password store fails. + * @param message The error message describing the failure. + */ + void deletePasswordStoreFailed(QString message); + private: QString m_password_store; /**< The path to the password store. */ std::unique_ptr m_gpg; /**< The GPG instance used for encryption/decryption. */ @@ -141,22 +159,22 @@ public: // GPG-related methods /** - * @brief Deletes the specified GPG key. + * @brief Launch the job to delete the specified GPG key. * @param key The PassKeyModel to delete. - * @return True if the operation was successful, false otherwise. + * @return True if the job was start successfully, false otherwise. */ Q_INVOKABLE bool deleteGPGKey(PassKeyModel* key); /** - * @brief Imports a GPG key from the given URL. + * @brief Launch the job to import a GPG key from the given URL. * @param url The URL to import the GPG key from. - * @return True if the operation was successful, false otherwise. + * @return True if the job was start was successfully, false otherwise. */ Q_INVOKABLE bool importGPGKey(QUrl url); /** - * @brief Retrieves all GPG keys. - * @return True if the operation was successful, false otherwise. + * @brief Launch the to retrieve all GPG keys. + * @return True if the job was start was successfully, false otherwise. */ Q_INVOKABLE bool getAllGPGKeys(); @@ -170,11 +188,17 @@ public: // Password store-related methods /** - * @brief Shows the password associated with the specified URL. + * @brief Launch the job to shows the password associated with the specified URL. * @param url The URL pointing to the password store entry. - * @return True if the operation was successful, false otherwise. + * @return True if the job was start successfully, false otherwise. */ Q_INVOKABLE bool show(QUrl url); + + /** + * @brief Launch the job to delete the password store. + * @return True if if the job was start successfully, false otherwise. + */ + Q_INVOKABLE bool deletePasswordStore(); }; #endif diff --git a/po/utpass.qrouland.pot b/po/utpass.qrouland.pot index fdae50c..2f7b458 100644 --- a/po/utpass.qrouland.pot +++ b/po/utpass.qrouland.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: utpass.qrouland\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-01-20 11:22+0100\n" +"POT-Creation-Date: 2025-01-20 14:43+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -79,26 +79,32 @@ msgstr "" msgid "Info" msgstr "" -#: ../qml/pages/PasswordList.qml:37 -msgid "" -"No password found
You can import a password store by cloning or importing " -"a zip in the settings" +#: ../qml/pages/PasswordList.qml:43 +msgid "No password found" msgstr "" -#: ../qml/pages/PasswordList.qml:71 ../qml/pages/settings/InfoKeys.qml:171 +#: ../qml/pages/PasswordList.qml:54 +msgid "You can import a password store by cloning or" +msgstr "" + +#: ../qml/pages/PasswordList.qml:60 +msgid "importing a password store zip in the settings" +msgstr "" + +#: ../qml/pages/PasswordList.qml:93 msgid "Decryption failed !" msgstr "" -#: ../qml/pages/PasswordList.qml:85 +#: ../qml/pages/PasswordList.qml:107 msgid "Back" msgstr "" -#: ../qml/pages/PasswordList.qml:92 ../qml/pages/headers/MainHeader.qml:9 +#: ../qml/pages/PasswordList.qml:114 ../qml/pages/headers/MainHeader.qml:9 #: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1 msgid "UTPass" msgstr "" -#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:70 +#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75 msgid "Settings" msgstr "" @@ -106,6 +112,35 @@ msgstr "" msgid "Search" msgstr "" +#: ../qml/pages/settings/DeleteRepo.qml:41 +#: ../qml/pages/settings/Settings.qml:58 +msgid "Delete Password Store" +msgstr "" + +#: ../qml/pages/settings/DeleteRepo.qml:57 +msgid "You're are about to delete
the current Password Store.
Continue ?" +msgstr "" + +#: ../qml/pages/settings/DeleteRepo.qml:58 +#: ../qml/pages/settings/ImportZip.qml:62 +#: ../qml/pages/settings/InfoKeys.qml:140 +#: ../qml/pages/settings/git/ImportGitClone.qml:55 +msgid "Yes" +msgstr "" + +#: ../qml/pages/settings/DeleteRepo.qml:71 +msgid "Password Store removal failed !" +msgstr "" + +#: ../qml/pages/settings/DeleteRepo.qml:80 +msgid "Password Store deleted !" +msgstr "" + +#: ../qml/pages/settings/DeleteRepo.qml:92 +#: ../qml/pages/settings/InfoKeys.qml:182 +msgid "Info Keys" +msgstr "" + #: ../qml/pages/settings/ImportKeyFile.qml:57 msgid "Key import failed !" msgstr "" @@ -123,18 +158,12 @@ msgid "" "Importing a new zip will delete
any existing password store!
Continue ?" msgstr "" -#: ../qml/pages/settings/ImportZip.qml:62 -#: ../qml/pages/settings/InfoKeys.qml:137 -#: ../qml/pages/settings/git/ImportGitClone.qml:54 -msgid "Yes" -msgstr "" - #: ../qml/pages/settings/ImportZip.qml:75 msgid "Password store import failed !" msgstr "" #: ../qml/pages/settings/ImportZip.qml:84 -#: ../qml/pages/settings/git/ImportGitClone.qml:76 +#: ../qml/pages/settings/git/ImportGitClone.qml:77 msgid "Password store sucessfully imported !" msgstr "" @@ -142,32 +171,32 @@ msgstr "" msgid "Zip Password Store Import" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:54 +#: ../qml/pages/settings/InfoKeys.qml:56 msgid "Key ID :" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:88 +#: ../qml/pages/settings/InfoKeys.qml:90 msgid "Users IDs : " msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:114 +#: ../qml/pages/settings/InfoKeys.qml:117 msgid "Delete this key" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:136 -msgid "You're are about to delete
%1
Continue ?" +#: ../qml/pages/settings/InfoKeys.qml:139 +msgid "You're are about to delete
%1
.Continue ?" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:150 +#: ../qml/pages/settings/InfoKeys.qml:153 msgid "Key removal failed !" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:159 +#: ../qml/pages/settings/InfoKeys.qml:162 msgid "Key successfully deleted !" msgstr "" -#: ../qml/pages/settings/InfoKeys.qml:179 -msgid "Info Keys" +#: ../qml/pages/settings/InfoKeys.qml:174 +msgid "An Error occured getting GPG keys !" msgstr "" #: ../qml/pages/settings/Settings.qml:23 @@ -194,7 +223,7 @@ msgstr "" msgid "Import a Password Store Zip" msgstr "" -#: ../qml/pages/settings/Settings.qml:62 +#: ../qml/pages/settings/Settings.qml:67 msgid "Warning: importing delete any exiting Password Store" msgstr "" @@ -212,16 +241,16 @@ msgstr "" msgid "Password" msgstr "" -#: ../qml/pages/settings/git/ImportGitClone.qml:53 +#: ../qml/pages/settings/git/ImportGitClone.qml:54 msgid "" "Importing a git repo will delete
any existing password store!" "
Continue ?" msgstr "" -#: ../qml/pages/settings/git/ImportGitClone.qml:67 +#: ../qml/pages/settings/git/ImportGitClone.qml:68 msgid "An error occured during git clone !" msgstr "" -#: ../qml/pages/settings/git/ImportGitClone.qml:88 +#: ../qml/pages/settings/git/ImportGitClone.qml:89 msgid "Git Clone Import" msgstr "" diff --git a/qml/pages/PasswordList.qml b/qml/pages/PasswordList.qml index 4190646..966eb5f 100644 --- a/qml/pages/PasswordList.qml +++ b/qml/pages/PasswordList.qml @@ -26,17 +26,39 @@ Page { }); } - Rectangle { + Column { anchors.top: passwordListHeader.bottom anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left - visible: folderModel.count == 0 + anchors.leftMargin: units.gu(2) + anchors.rightMargin: units.gu(2) + + Rectangle { + width: parent.width + height: units.gu(1) + } Text { - text: i18n.tr("No password found
You can import a password store by cloning or importing a zip in the settings") - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter + text: i18n.tr("No password found") + width: parent.width + horizontalAlignment: Text.AlignHCenter + } + + Rectangle { + width: parent.width + height: units.gu(1) + } + + Text { + text: i18n.tr("You can import a password store by cloning or") + width: parent.width + horizontalAlignment: Text.AlignHCenter + } + + Text { + text: i18n.tr("importing a password store zip in the settings") + width: parent.width horizontalAlignment: Text.AlignHCenter } diff --git a/qml/pages/settings/DeleteRepo.qml b/qml/pages/settings/DeleteRepo.qml new file mode 100644 index 0000000..3b9855a --- /dev/null +++ b/qml/pages/settings/DeleteRepo.qml @@ -0,0 +1,92 @@ +import "../../components" +import "../../dialogs" +import "../headers" +import Lomiri.Components 1.3 +import Lomiri.Components.Popups 1.3 +import Pass 1.0 +import QtQuick 2.4 + +Page { + id: deleteRepoPage + + Component.onCompleted: { + Pass.deletePasswordStoreSucceed.connect(function(keys_info) { + PopupUtils.open(deleteRepoPagePageDeleteSuccess); + }); + Pass.deletePasswordStoreFailed.connect(function(message) { + PopupUtils.open(deleteRepoPagePageDeleteError); + }); + } + + Column { + id: deleteRepoPageListView + + anchors.top: deleteRepoPageHeader.bottom + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.left: parent.left + anchors.leftMargin: units.gu(2) + anchors.rightMargin: units.gu(2) + spacing: units.gu(1) + + Rectangle { + width: parent.width + height: units.gu(1) + } + + Button { + id: buttonDeleteKey + + width: parent.width + text: i18n.tr("Delete Password Store") + color: theme.palette.normal.negative + onClicked: { + PopupUtils.open(deleteRepoPagePageDeleteValidation, deleteRepoPage); + } + } + + } + + Component { + id: deleteRepoPagePageDeleteValidation + + SimpleValidationDialog { + text: i18n.tr("You're are about to delete
the current Password Store.
Continue ?") + continueText: i18n.tr("Yes") + continueColor: theme.palette.normal.negative + onValidated: { + var status = Pass.deletePasswordStore(); + } + } + + } + + Component { + id: deleteRepoPagePageDeleteError + + ErrorDialog { + textError: i18n.tr("Password Store removal failed !") + } + + } + + Component { + id: deleteRepoPagePageDeleteSuccess + + SuccessDialog { + textSuccess: i18n.tr("Password Store deleted !") + onDialogClosed: { + pageStack.pop(); + pageStack.pop(); + } + } + + } + + header: StackHeader { + id: deleteRepoPageHeader + + title: i18n.tr('Info Keys') + } + +} diff --git a/qml/pages/settings/InfoKeys.qml b/qml/pages/settings/InfoKeys.qml index fc0aa2f..537b375 100644 --- a/qml/pages/settings/InfoKeys.qml +++ b/qml/pages/settings/InfoKeys.qml @@ -12,7 +12,7 @@ Page { property QtObject currentKey Component.onCompleted: { - Pass.onGetAllGPGKeysSucceed.connect(function(keys_info) { + Pass.getAllGPGKeysSucceed.connect(function(keys_info) { infoKeysListView.model = keys_info; }); Pass.getAllGPGKeysFailed.connect(function(message) { @@ -34,6 +34,8 @@ Page { anchors.bottom: parent.bottom anchors.right: parent.right anchors.left: parent.left + anchors.leftMargin: units.gu(2) + anchors.rightMargin: units.gu(2) delegate: Grid { columns: 1 @@ -111,6 +113,7 @@ Page { Button { id: buttonDeleteKey + width: parent.width text: i18n.tr("Delete this key") color: theme.palette.normal.negative onClicked: { @@ -133,7 +136,7 @@ Page { id: infoKeysPageDeleteValidation SimpleValidationDialog { - text: i18n.tr("You're are about to delete
%1
Continue ?").arg(infoKeysPage.currentKey.uid) + text: i18n.tr("You're are about to delete
%1
.Continue ?").arg(infoKeysPage.currentKey.uid) continueText: i18n.tr("Yes") continueColor: theme.palette.normal.negative onValidated: { @@ -168,7 +171,7 @@ Page { id: infoKeysPageGetAllError ErrorDialog { - textError: i18n.tr("Decryption failed !") + textError: i18n.tr("An Error occured getting GPG keys !") } } diff --git a/qml/pages/settings/Settings.qml b/qml/pages/settings/Settings.qml index d479f69..bbdba01 100644 --- a/qml/pages/settings/Settings.qml +++ b/qml/pages/settings/Settings.qml @@ -53,6 +53,11 @@ Page { text: i18n.tr('Import a Password Store Zip') } + PageStackLink { + page: Qt.resolvedUrl("DeleteRepo.qml") + text: i18n.tr('Delete Password Store') + } + Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/qml/pages/settings/git/GitModeOptionSelector.qml b/qml/pages/settings/git/GitModeOptionSelector.qml index 54ba89c..b7f9a97 100644 --- a/qml/pages/settings/git/GitModeOptionSelector.qml +++ b/qml/pages/settings/git/GitModeOptionSelector.qml @@ -11,12 +11,12 @@ OptionSelector { onDelegateClicked: function(i) { if (i === 0) timer.setTimeout(function() { - importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml"); - }, 500); + importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml"); + }, 500); else if (i === 1) timer.setTimeout(function() { - importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml"); - }, 500); + importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml"); + }, 500); } Timer { diff --git a/qml/pages/settings/git/ImportGitClone.qml b/qml/pages/settings/git/ImportGitClone.qml index 4cf0e73..3999ceb 100644 --- a/qml/pages/settings/git/ImportGitClone.qml +++ b/qml/pages/settings/git/ImportGitClone.qml @@ -11,10 +11,10 @@ Page { id: importGitClonePage Component.onCompleted: { - Git.onCloneSucceed.connect(function() { + Git.cloneSucceed.connect(function() { PopupUtils.open(dialogGitCloneSuccess); }); - Git.onCloneFailed.connect(function() { + Git.cloneFailed.connect(function() { PopupUtils.open(dialogGitCloneError); }); PopupUtils.open(importGitCloneValidation, importGitClonePage);