diff --git a/plugins/Git/CMakeLists.txt b/plugins/Git/CMakeLists.txt index 3001484..5f06fe8 100644 --- a/plugins/Git/CMakeLists.txt +++ b/plugins/Git/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set(PLUGIN "Git") set( diff --git a/plugins/Git/git.cpp b/plugins/Git/git.cpp index dc0557f..1d8910c 100644 --- a/plugins/Git/git.cpp +++ b/plugins/Git/git.cpp @@ -3,31 +3,81 @@ #include #include + #include "git.h" #include "libgit.h" -bool Git::clone(QString url, QString destination_dir_path) +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; + + +QDir Git::clone_setup() { - qInfo() << "Cloning " << url << " to destination " << destination_dir_path; - QDir tmp_dir(QStandardPaths::writableLocation( - QStandardPaths::CacheLocation).append("/clone")); + QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone")); + tmp_dir.removeRecursively(); qDebug() << "Temp dir path is " << tmp_dir.absolutePath(); + return tmp_dir; +} + + +bool Git::clone_tear_down(QDir tmp_dir) +{ + tmp_dir.removeRecursively(); +} + +bool Git::move_to_destination(QString path, QDir tmp_dir) +{ + qDebug() << "Removing password_store " << path; + QDir destination_dir(path); + destination_dir.removeRecursively(); + + qDebug() << "Moving cloned content to destination dir"; + QDir dir; + qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath(); + return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling +} + + + +bool Git::clone(QString url, QString path, mode_type mode) //, GitPlugin::RepoType type, QString pass) +{ + auto v = overload { + [](const Unset& x) { return "Unset"; }, + [](const HTTP& x) { return "Unset"; }, + [](const HTTPAuth& x) { return "HTTPAuth"; }, + [](const SSHAuth& x) { return "SSHAuth"; }, + [](const SSHKey& x) { return "SSHKey"; }, + }; + qInfo() << "Cloning " << url << " to destination " << path << " using " << std::visit(v, mode); + + LibGit::instance()->set_mode(mode); + auto tmp_dir = this->clone_setup(); + qDebug() << "Cloning " << url << " to tmp dir " << tmp_dir.absolutePath(); auto ret = LibGit::instance()->clone(url, tmp_dir.absolutePath()); // TODO Better error handling - if (ret) { - qDebug() << "Removing password_store " << destination_dir_path; - QDir destination_dir(destination_dir_path); - destination_dir.removeRecursively(); + if (ret) { this->move_to_destination(path, tmp_dir);} + + tmp_dir.removeRecursively(); + LibGit::instance()->set_mode(Unset()); - qDebug() << "Moving cloned content to destination dir"; - QDir dir; - qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath(); - ret = dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling - } - //tmp_dir.removeRecursively(); return ret ; } + +bool Git::clone_http(QString url, QString path) //, GitPlugin::RepoType type, QString pass) +{ + HTTP mode = {}; + return this->clone(url, path, mode); +} + +bool Git::clone_http_pass(QString url, QString path, QString pass) { + HTTPAuth mode = { pass }; + return this->clone(url, path, mode); +} diff --git a/plugins/Git/git.h b/plugins/Git/git.h index e6ba7bb..260f5b5 100644 --- a/plugins/Git/git.h +++ b/plugins/Git/git.h @@ -3,17 +3,29 @@ #include #include +#include +#include "libgit.h" class Git : public QObject { Q_OBJECT +private: + QDir clone_setup(); + bool move_to_destination(QString path, QDir tmp_dir); + bool clone_tear_down(QDir tmp_dir); + bool clone(QString url, QString path, mode_type mode); + + public: Git() = default; ~Git() override = default; - Q_INVOKABLE bool clone(QString url, QString path); + Q_INVOKABLE bool clone_http(QString url, QString path); + Q_INVOKABLE bool clone_http_pass(QString url, QString path, QString pass); + // Q_INVOKABLE bool clone_ssh_pass(QString url, QString path, QString pass); + // Q_INVOKABLE bool clone_ssh_key(QString url, QString path, QString pub_key, QString priv_key, QString passphrase); // Q_INVOKABLE bool update(QUrl url, QString path); }; diff --git a/plugins/Git/libgit.cpp b/plugins/Git/libgit.cpp index 8cc506c..c300e56 100644 --- a/plugins/Git/libgit.cpp +++ b/plugins/Git/libgit.cpp @@ -1,5 +1,7 @@ #include #include + +#include extern "C" { #include } @@ -7,6 +9,12 @@ extern "C" { #include "libgit.h" +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; LibGit::LibGit() { @@ -18,39 +26,63 @@ LibGit::~LibGit() git_libgit2_shutdown(); } +void LibGit::set_mode(mode_type type) { + this->mode = type; +} + int LibGit::credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload) { - int error; - const char *user, *pass; - - /* - * Ask the user via the UI. On error, store the information and return GIT_EUSER which will be - * bubbled up to the code performing the fetch or push. Using GIT_EUSER allows the application - * to know it was an error from the application instead of libgit2. - */ - // if ((error = ask_user(&user, &pass, url, username_from_url, allowed_types)) < 0) { - // store_error(error); - // return GIT_EUSER; - // } - user = "pass"; - pass = "pass"; - return git_cred_userpass_plaintext_new(out, user, pass); - // return GIT_EUSER; + // TODO : More precise Error Handling for UI + auto instance = LibGit::instance(); + auto v = overload { + [](const Unset& x) { + qDebug() << "credentials_cb : Unset "; + qWarning() << "credentials_cb : callback should never be call for Unset "; + return (int) GIT_EUSER; + }, + [](const HTTP& x) { + qDebug() << "credentials_cb : HTTP "; + qWarning() << "credentials_cb : callback should never be call for HTTP "; + return (int) GIT_EUSER; + }, + [&out, &username_from_url](const HTTPAuth& x) { + qDebug() << "credentials_cb : HTTPAuth "; + if(!username_from_url) { + qWarning() << "credentials_cb : no username provided "; + return (int) GIT_EUSER; + } + return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData()); + }, + [&](const SSHAuth& x) { + qWarning() << "credentials_cb : SSHAuth to be implemented "; + return (int) GIT_EUSER; + }, // TODO + [&](const SSHKey& x) { + qWarning() << "credentials_cb : SSHKey to be implemented "; + return (int) GIT_EUSER; + } // TODO + }; + return std::visit(v, instance->mode); } + bool LibGit::clone(QString url, QString path) { git_repository *repo = NULL; git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.credentials = *credentials_cb; + int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts); if (ret != 0) { - qDebug() << git_error_last()->message; + qDebug() << git_error_last()->message; } if (repo) { - git_repository_free(repo); + git_repository_free(repo); } - return ret == 0; // TODO Clean error handling to return specifics errors for the ui + + return ret; } + diff --git a/plugins/Git/libgit.h b/plugins/Git/libgit.h index dc4ab0d..cb02f5d 100644 --- a/plugins/Git/libgit.h +++ b/plugins/Git/libgit.h @@ -3,19 +3,30 @@ #include #include +#include extern "C" { #include } #include +#include + +struct Unset { }; +struct HTTP { }; +struct HTTPAuth { QString pass; }; +struct SSHAuth { }; +struct SSHKey { }; +typedef std::variant mode_type; class LibGit { private: LibGit(); + + mode_type mode; + static int credentials_cb(git_cred **out, const char *url, const char *username_from_url, unsigned int allowed_types, void *payload); - public: ~LibGit(); static std::shared_ptr instance() @@ -27,6 +38,7 @@ public: void operator=(LibGit const &) = delete; bool clone(QString url, QString path); + void set_mode(mode_type type); }; #endif diff --git a/plugins/Git/plugin.cpp b/plugins/Git/plugin.cpp index 51166ec..1ebbf6f 100644 --- a/plugins/Git/plugin.cpp +++ b/plugins/Git/plugin.cpp @@ -1,10 +1,13 @@ #include +#include #include "plugin.h" #include "git.h" + void GitPlugin::registerTypes(const char *uri) { //@uri Git qmlRegisterSingletonType(uri, 1, 0, "Git", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Git; }); + } diff --git a/plugins/Pass/CMakeLists.txt b/plugins/Pass/CMakeLists.txt index a650a9d..9c2041a 100644 --- a/plugins/Pass/CMakeLists.txt +++ b/plugins/Pass/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set(PLUGIN "Pass") set( diff --git a/plugins/Utils/CMakeLists.txt b/plugins/Utils/CMakeLists.txt index 295bebb..7c2a389 100644 --- a/plugins/Utils/CMakeLists.txt +++ b/plugins/Utils/CMakeLists.txt @@ -1,4 +1,4 @@ -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") set(PLUGIN "Utils") set( diff --git a/po/utpass.qrouland.pot b/po/utpass.qrouland.pot index 7d7a88f..73369c7 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-10 21:31+0100\n" +"POT-Creation-Date: 2025-01-13 17:55+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -87,7 +87,8 @@ msgstr "" #: ../qml/pages/PasswordList.qml:26 msgid "" -"No password found
You can import a password store zip in the settings" +"No password found
You can import a password store by cloning or importing " +"a zip in the settings" msgstr "" #: ../qml/pages/PasswordList.qml:65 @@ -111,30 +112,30 @@ msgstr "" msgid "Repo Url" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:45 -msgid "Git repo url" +#: ../qml/pages/settings/ImportGitClone.qml:53 +msgid "Password" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:52 +#: ../qml/pages/settings/ImportGitClone.qml:69 msgid "Clone" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:68 +#: ../qml/pages/settings/ImportGitClone.qml:91 msgid "" "Importing a git repo will delete
any existing password store!" "
Continue ?" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:80 +#: ../qml/pages/settings/ImportGitClone.qml:103 msgid "An error occured during git clone !" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:89 +#: ../qml/pages/settings/ImportGitClone.qml:112 #: ../qml/pages/settings/ImportZip.qml:82 msgid "Password store sucessfully imported !" msgstr "" -#: ../qml/pages/settings/ImportGitClone.qml:101 +#: ../qml/pages/settings/ImportGitClone.qml:124 msgid "Git Clone Import" msgstr "" diff --git a/qml/Main.qml b/qml/Main.qml index 1f22bcc..a9f26e8 100644 --- a/qml/Main.qml +++ b/qml/Main.qml @@ -19,10 +19,10 @@ MainView { //TODO use parameters to impove passphrase dialog var passphraseDialog = PopupUtils.open(Qt.resolvedUrl("dialogs/PassphraseDialog.qml")); passphraseDialog.activateFocus(); - var validated = function validated(passphrase) { + var validated = function (passphrase) { responsePassphraseDialog(false, passphrase); }; - var canceled = function canceled() { + var canceled = function () { responsePassphraseDialog(true, ""); }; passphraseDialog.validated.connect(validated); diff --git a/qml/pages/PasswordList.qml b/qml/pages/PasswordList.qml index b87cba5..8dd8a96 100644 --- a/qml/pages/PasswordList.qml +++ b/qml/pages/PasswordList.qml @@ -23,7 +23,7 @@ Page { visible: folderModel.count == 0 Text { - text: i18n.tr("No password found
You can import a password store zip in the settings") + 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 horizontalAlignment: Text.AlignHCenter diff --git a/qml/pages/settings/ImportGitClone.qml b/qml/pages/settings/ImportGitClone.qml index 3d5c813..6a426b8 100644 --- a/qml/pages/settings/ImportGitClone.qml +++ b/qml/pages/settings/ImportGitClone.qml @@ -28,7 +28,7 @@ Page { } Text { - id: repoUrlLabe + id: repoUrlLabel horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -37,12 +37,29 @@ Page { } TextField { - id: textFieldInput + id: repoUrlInput horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter width: parent.width - placeholderText: i18n.tr('Git repo url') + } + + Text { + id: repoPasswordLabel + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + text: i18n.tr('Password') + } + + TextField { + id: repoPasswordInput + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + width: parent.width + echoMode: TextInput.Password } Button { @@ -51,7 +68,13 @@ Page { width: parent.width text: i18n.tr('Clone') onClicked: { - var ret = Git.clone(textFieldInput.text, Pass.password_store); + var ret = false; + if(repoPasswordInput.text === "") { + ret = Git.clone_http(repoUrlInput.text, Pass.password_store); + } else { + ret = Git.clone_http_pass(repoUrlInput.text, Pass.password_store, repoPasswordInput.text); + } + if (ret) PopupUtils.open(dialogImportGitCloneSuccess); else