diff --git a/plugins/Git/git.cpp b/plugins/Git/git.cpp index bf787e1..4fe73e2 100644 --- a/plugins/Git/git.cpp +++ b/plugins/Git/git.cpp @@ -41,7 +41,7 @@ bool Git::clone(QString url, QString path, cred_type mode) [](const HTTPUserPass & x) { UNUSED(x); return "HTTPAuth"; }, [](const SSHKey & x) { UNUSED(x); return "SSHKey"; }, }; - qDebug() << "Creating clone Job " << url << " " << path << " " << std::visit(v, mode); + qDebug() << "[Git] Creating clone Job " << url << " " << path << " " << std::visit(v, mode); CloneJob *clone_job = new CloneJob(url, path, mode); connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult); connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater); @@ -65,7 +65,7 @@ bool Git::cloneHttpPass(QString url, QString path, QString pass) bool Git::cloneSshKey(QString url, QString path, QString passphrase) { - qInfo() << "[Git] Call clone command HttpPass " << url << " " << path; + qInfo() << "[Git] Call clone command SshKey " << url << " " << path; SSHKey mode = { this->pubKeyPath(), this->privKeyPath(), passphrase }; return this->clone(url, path, mode); diff --git a/plugins/Git/git.h b/plugins/Git/git.h index 5e2a979..61fe65b 100644 --- a/plugins/Git/git.h +++ b/plugins/Git/git.h @@ -56,7 +56,7 @@ private: * @brief Clones a repository from a specified URL. * * This private method initiates the cloning job. It sets up the repository cloning process based on - * the specified URL, destination path, and cloning mode (HTTP or SSH). + * the specified URL, destination path, and cloning mode (HTTP, HTTP_AUTH or SSH). * * @param url The URL of the Git repository to clone. * @param path The destination path for the cloned repository. @@ -90,14 +90,14 @@ public: /** * @brief Constructor for the Git class. * - * Initializes the `Git` class, setting up necessary resources such as the semaphore for concurrent operation management. + * Initializes the `Git` class. */ Git(); /** * @brief Destructor for the Git class. * - * Cleans up any resources used by the `Git` class, ensuring that no ongoing operations or resources are left hanging. + * Cleans up any resources used by the `Git` class. */ ~Git() override; @@ -108,11 +108,10 @@ public: * @brief Clones a repository over HTTP. * * This method clones a Git repository from the specified HTTP URL and saves it to the given destination path. - * It is a wrapper around the private `clone()` method, specifying the HTTP cloning mode. * * @param url The HTTP URL of the Git repository to clone. * @param path The destination path for the cloned repository. - * @return `true` if the clone operation was successful, `false` otherwise. + * @return `true` if the clone operation was successfully started, `false` otherwise. */ Q_INVOKABLE bool cloneHttp(QString url, QString path); diff --git a/plugins/Git/jobs/clonejob.cpp b/plugins/Git/jobs/clonejob.cpp index 70cf6d3..744e59f 100644 --- a/plugins/Git/jobs/clonejob.cpp +++ b/plugins/Git/jobs/clonejob.cpp @@ -37,7 +37,7 @@ QDir CloneJob::cloneSetup() QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone")); tmp_dir.removeRecursively(); - qDebug() << "[CloneJob]Temp dir path is " << tmp_dir.absolutePath(); + qDebug() << "[CloneJob] Temp dir path is " << tmp_dir.absolutePath(); return tmp_dir; } @@ -64,9 +64,10 @@ bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire { git_repository *repo = NULL; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + PayloadCB payload = PayloadCB(false, cred); opts.fetch_opts.callbacks.credentials = cb; - opts.fetch_opts.callbacks.payload = &cred; + opts.fetch_opts.callbacks.payload = &payload; int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts); if (ret == GIT_EUSER ) { diff --git a/plugins/Git/jobs/gitjob.cpp b/plugins/Git/jobs/gitjob.cpp index 18c5356..7f978fb 100644 --- a/plugins/Git/jobs/gitjob.cpp +++ b/plugins/Git/jobs/gitjob.cpp @@ -24,26 +24,32 @@ int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_ unsigned int allowed_types, void *payload) { UNUSED(url); - cred_type *cred = (cred_type *)payload; + PayloadCB *p = (PayloadCB *)payload; - if (!username_from_url) { - qWarning() << "[GitJob] credentials_cb : no username provided "; + if (!username_from_url) { // we required here that the username must be provided directly in url (maybe improve later on) + qWarning() << "[GitJob] credentials_cb : no username provided"; return (int) GIT_EUSER; } + if (p->called) { + qWarning() << "[GitJob] credentials_cb : cb already called, probably invalid creds"; + return (int) GIT_EUSER; + } + p->called = true; + auto v = overload { [](const HTTP & x) { UNUSED(x); - qDebug() << "[GitJob] credentialsCB : None "; - qWarning() << "[GitJob] credentialsCB : callback should never be call for None "; + qDebug() << "[GitJob] credentialsCB : HTTP "; + qWarning() << "[GitJob] credentialsCB : callback should never be call for HTTP"; return (int) GIT_EUSER; }, [allowed_types, &out, &username_from_url](const HTTPUserPass & x) { qDebug() << "[GitJob] credentialsCB : HTTPUserPass "; if (!(allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)) { - qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass "; + qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass creds"; return (int) GIT_EUSER; } return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData()); @@ -52,13 +58,17 @@ int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_ { qDebug() << "[GitJob] credentialsCB : SSHKey "; if (!(allowed_types & GIT_CREDTYPE_SSH_KEY)) { - qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass "; + qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for SSHKey creds"; return (int) GIT_EUSER; } + qDebug() << "[GitJob] username_from_url :" << username_from_url; + qDebug() << "[GitJob] pub_key :" << x.pub_key.toLocal8Bit().constData(); + qDebug() << "[GitJob] priv_key :" << x.priv_key.toLocal8Bit().constData(); + qDebug() << "[GitJob] passphrase :" << x.passphrase.toLocal8Bit().constData(); return git_cred_ssh_key_new(out, username_from_url, x.pub_key.toLocal8Bit().constData(), x.priv_key.toLocal8Bit().constData(), x.passphrase.toLocal8Bit().constData()); } }; - auto error = std::visit(v, *cred); - return error; + auto ret = std::visit(v, p->creds); + return ret; } diff --git a/plugins/Git/jobs/gitjob.h b/plugins/Git/jobs/gitjob.h index 76b0e4b..294f27b 100644 --- a/plugins/Git/jobs/gitjob.h +++ b/plugins/Git/jobs/gitjob.h @@ -31,6 +31,14 @@ struct SSHKey { */ typedef std::variant cred_type; + +struct PayloadCB +{ + bool called; + cred_type creds; + PayloadCB(bool ca, cred_type cr): called(ca), creds(cr) {} +}; + /** * @class GitJob * @brief A class that manages Git-related tasks using libgit2. diff --git a/plugins/Pass/CMakeLists.txt b/plugins/Pass/CMakeLists.txt index 38ff285..e42644d 100644 --- a/plugins/Pass/CMakeLists.txt +++ b/plugins/Pass/CMakeLists.txt @@ -7,6 +7,7 @@ set( pass.cpp passkeyringmodel.h passphraseprovider.h + error.h jobs/decryptjob.cpp jobs/deletekeyjob.cpp jobs/getkeysjob.cpp diff --git a/plugins/Pass/error.h b/plugins/Pass/error.h new file mode 100644 index 0000000..0e52f95 --- /dev/null +++ b/plugins/Pass/error.h @@ -0,0 +1,30 @@ +// error.h +#ifndef ERROR_H +#define ERROR_H + +extern "C" { +#include "rnp/rnp_err.h" +} + +enum ErrorCodeShow { + UnexceptedError= 1, + BadPassphrase, + NoKeyFound, + DecryptFailed +}; + +ErrorCodeShow rnpErrorToErrorCodeShow(int rnpErrorCode) { + switch (rnpErrorCode) { + case RNP_ERROR_BAD_PASSWORD: + return BadPassphrase; + case RNP_ERROR_KEY_NOT_FOUND: + case RNP_ERROR_NO_SUITABLE_KEY: + return NoKeyFound; + case RNP_ERROR_DECRYPT_FAILED: + return DecryptFailed; + default: + return UnexceptedError; + } +} + +#endif // ERROR_H diff --git a/plugins/Pass/pass.cpp b/plugins/Pass/pass.cpp index 32cf0bc..9823237 100644 --- a/plugins/Pass/pass.cpp +++ b/plugins/Pass/pass.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "error.h" #include "jobs/decryptjob.h" #include "jobs/deletekeyjob.h" #include "jobs/getkeysjob.h" @@ -108,7 +109,7 @@ bool Pass::show(QUrl url) void Pass::slotShowError(rnp_result_t err) { qInfo() << "[Pass] Show Failed"; - emit showFailed(rnp_result_to_string(err)); + emit showFailed(rnpErrorToErrorCodeShow(err), rnp_result_to_string(err)); this->m_sem->release(1); } @@ -136,13 +137,13 @@ bool Pass::deletePasswordStore() } void Pass::slotDeletePasswordStoreResult(bool err) -{ - this->initPasswordStore(); // reinit an empty password-store +{ if (err) { qInfo() << "[Pass] Delete Password Store Failed"; emit deletePasswordStoreFailed("failed to delete password store"); } else { qInfo() << "[Pass] Delete Password Store Succeed"; + this->initPasswordStore(); // reinit an empty password-store emit deletePasswordStoreSucceed(); } this->m_sem->release(1); diff --git a/plugins/Pass/pass.h b/plugins/Pass/pass.h index 2132c41..8bf610f 100644 --- a/plugins/Pass/pass.h +++ b/plugins/Pass/pass.h @@ -125,7 +125,7 @@ signals: * @brief Emitted when showing a password fails. * @param message The error message describing the failure. */ - void showFailed(QString message); + void showFailed(int err, QString message); /** * @brief Emitted hen showing a password cancelled. diff --git a/plugins/Utils/utils.cpp b/plugins/Utils/utils.cpp index e0a5b1a..b44990e 100644 --- a/plugins/Utils/utils.cpp +++ b/plugins/Utils/utils.cpp @@ -59,9 +59,9 @@ bool Utils::rmDir(QUrl dir_url) bool Utils::fileExists(QUrl path) { - QString p = path.toLocalFile(); + QString p = path.toString(); auto ret = QFileInfo::exists(p) && QFileInfo(p).isFile(); - qDebug() << "[Utils]" << path << " existing file :" << ret; + qDebug() << "[Utils]" << path << "existing file :" << ret; return ret; } diff --git a/po/utpass.qrouland.pot b/po/utpass.qrouland.pot index 8bb51ec..58e4bdb 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-02-21 15:49+0100\n" +"POT-Creation-Date: 2025-03-12 15:16+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -75,11 +75,11 @@ msgstr "" msgid "File Import" msgstr "" -#: ../qml/dialogs/ErrorDialog.qml:12 +#: ../qml/dialogs/ErrorDialog.qml:13 msgid "Error !" msgstr "" -#: ../qml/dialogs/ErrorDialog.qml:16 +#: ../qml/dialogs/ErrorDialog.qml:17 msgid "Close" msgstr "" @@ -137,27 +137,35 @@ msgstr "" msgid "Info" msgstr "" -#: ../qml/pages/PasswordList.qml:79 -msgid "No password found" +#: ../qml/pages/PasswordList.qml:58 +msgid "Bad passphrase" +msgstr "" + +#: ../qml/pages/PasswordList.qml:61 +msgid "No valid key found" msgstr "" #: ../qml/pages/PasswordList.qml:92 +msgid "No password found" +msgstr "" + +#: ../qml/pages/PasswordList.qml:105 msgid "You can import a password store by cloning or" msgstr "" -#: ../qml/pages/PasswordList.qml:99 +#: ../qml/pages/PasswordList.qml:112 msgid "importing a password store zip in the settings" msgstr "" -#: ../qml/pages/PasswordList.qml:176 +#: ../qml/pages/PasswordList.qml:189 msgid "Decryption failed !" msgstr "" -#: ../qml/pages/PasswordList.qml:198 +#: ../qml/pages/PasswordList.qml:213 msgid "Back" msgstr "" -#: ../qml/pages/PasswordList.qml:205 ../qml/pages/headers/MainHeader.qml:14 +#: ../qml/pages/PasswordList.qml:220 ../qml/pages/headers/MainHeader.qml:14 #: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1 msgid "UTPass" msgstr "" diff --git a/qml/components/GitCloneSshKey.qml b/qml/components/GitCloneSshKey.qml index b0dc964..cec84f7 100644 --- a/qml/components/GitCloneSshKey.qml +++ b/qml/components/GitCloneSshKey.qml @@ -142,7 +142,7 @@ Column { color: theme.palette.normal.positive text: i18n.tr('Clone') onClicked: { - Git.cloneSSHKey(repoUrlInput.text, Pass.Passphrase_store, repoPassphraseInput.text); + Git.cloneSshKey(repoUrlInput.text, Pass.password_store, repoPassphraseInput.text); } } diff --git a/qml/dialogs/ErrorDialog.qml b/qml/dialogs/ErrorDialog.qml index 5edf7ae..665bfe4 100644 --- a/qml/dialogs/ErrorDialog.qml +++ b/qml/dialogs/ErrorDialog.qml @@ -6,11 +6,12 @@ Dialog { id: dialog property string textError + property string textErrorDescription : null signal dialogClosed() title: i18n.tr("Error !") - text: textError + text: textErrorDescription ? (textError + "
" + textErrorDescription) : textError Button { text: i18n.tr("Close") diff --git a/qml/pages/PasswordList.qml b/qml/pages/PasswordList.qml index 2ee77e9..f008bf5 100644 --- a/qml/pages/PasswordList.qml +++ b/qml/pages/PasswordList.qml @@ -12,6 +12,7 @@ Page { property string __passwordStorePath property var __passwords + property string __text_error_description: null function __searchPasswords(filter) { var ret = []; @@ -48,7 +49,25 @@ Page { "title": filename }); }); - Pass.onShowFailed.connect(function(message) { + Pass.onShowFailed.connect(function(code, message) { + switch (code) { + case 1: // UnexceptedError -> use the default (not translate) rnp error + __text_error_description = message; + break; + case 2: // BadPassphrase + __text_error_description = i18n.tr("Bad passphrase"); + break; + case 3: // NoKeyFound + __text_error_description = i18n.tr("No valid key found"); + break; + case 3: // DecryptFailed + __text_error_description = i18n.tr("Decryption failed"); + break; + default: + console.warn("Unhandled error code"); + __text_error_description = message; + break; + } PopupUtils.open(passwordPageDecryptError); }); Pass.onLsSucceed.connect(function(passwords) { @@ -174,10 +193,12 @@ Page { ErrorDialog { textError: i18n.tr("Decryption failed !") + textErrorDescription: __text_error_description } - } + + Timer { id: searchTimer