1
0
mirror of https://github.com/QRouland/UTPass.git synced 2026-01-09 19:16:57 +00:00

9 Commits
main ... 0.0.4

25 changed files with 468 additions and 297 deletions

View File

@@ -1,4 +1,9 @@
# 0.0.3: Git Initial Support and Move to RNP
# 0.0.4
- Fix white screen issue on 24.04
- Add search functionality
- Some minor fixes and improvements
# 0.0.3
- Port app to Focal
- Improve UI :
* Follow human interface guidelines
@@ -9,14 +14,14 @@
- Add Git HTTP and HTTP AUTH clone for password store import feature
- Add delete password store feature
# 0.0.2 : Added translations
# 0.0.2
- Added French by Anne17 and Reda
- Added Catalan by Joan CiberSheep
- Added Spanish by Advocatux and Reda
Thanks to all the translators !
# 0.0.1 : Initial Release
# 0.0.1
- Import of gpg keys via file
- Suppression of gpg keys
- Import of password via a password store zip

View File

@@ -1,6 +1,6 @@
# UTPass
A Ubuntu Touch password management app aiming to be compatible with [ZX2C4s pass command line application](https://www.passwordstore.org/) the standard unix password manager.
A Ubuntu Touch password management app aiming to be read-only compatible with [ZX2C4s pass command line application](https://www.passwordstore.org/) the standard unix password manager.
## Installation
@@ -9,10 +9,6 @@ UTPass is avalaible on the [OpenStore](https://open-store.io)
[![OpenStore](https://open-store.io/badges/en_US.png)](https://open-store.io/app/utpass.qrouland)
## Features
The goal is to be closest possible of the features offer by [ZX2C4s pass command line application](https://www.passwordstore.org/).
See [Features wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/contributing) for details.
## Export/Import
@@ -23,8 +19,7 @@ Export gpg private keys in order to decrypt passwords:
gpg --output keys.gpg --export-secret-keys <key>
```
If your password store is already hosted in a Git repository that provides HTTP or HTTP with authentication for cloning (we're working to have support for SSH soon), you can clone your password store directly from the app.
Otherwise, follow these steps to export it to a ZIP file for importing.
If your password store is already hosted in a Git repository with HTTP (SSH is not supported yet), you can clone your password store directly from the app. Otherwise, follow these steps to export it as a ZIP file for importing.
Export passwords to a ZIP archive, assuming they reside in the *.password-store* folder:
```

View File

@@ -41,4 +41,5 @@ install_lib:
- "libhttp_parser.so*"
- "libssh2.so*"
- "libquazip5.so*"
- "libjson-c.so*"

View File

@@ -10,7 +10,7 @@
"content-hub": "UTPass.contenthub"
}
},
"version": "0.0.4-dev",
"version": "0.0.4",
"maintainer": "Quentin Rouland <quentin@qrouland.com>",
"framework" : "ubuntu-sdk-20.04"
}

View File

@@ -6,6 +6,7 @@ set(
plugin.cpp
git.cpp
utils.h
error.h
jobs/clonejob.cpp
jobs/gitjob.cpp
)

55
plugins/Git/error.h Normal file
View File

@@ -0,0 +1,55 @@
// error.h
#ifndef ERROR_H
#define ERROR_H
#include <string>
extern "C" {
#include <git2.h>
}
// Enum for Git-specific errors (e.g., from git_clone)
enum class GitCloneErrorCode {
Successful = 0,
UnexpectedError, ///< Unknown or unexpected error
InvalidUrl, ///< Malformed URL error
NoUsername, ///< Missing username error
AuthentificationError, ///< Authentification error
UrlTypeDoNotMatchCreds,
};
/**
* Convert a git_error to a GitCloneErrorCode
* @param error A pointer to the git_error structure
* @return Corresponding GitCloneErrorCode integer value
*/
inline GitCloneErrorCode gitErrorToGitCloneErrorCode(const git_error* error)
{
if (error == nullptr) {
return GitCloneErrorCode::Successful; ///< Default error if null
}
if (error->message != nullptr) {
if (std::string(error->message) == "malformed URL") {
return GitCloneErrorCode::InvalidUrl; ///< Invalid URL error
}
if (std::string(error->message) == "remote requested authentication but did not negotiate mechanisms") {
return GitCloneErrorCode::AuthentificationError; ///< Invalid URL error
}
}
return GitCloneErrorCode::UnexpectedError; ///< Default to UnexpectedError
}
/**
* Maps the given GitCloneErrorCode to its corresponding integer.
*
* @param error The GitCloneErrorCode value to convert.
* @return Corresponding GitCloneErrorCode integer value.
*/
constexpr int code_err(GitCloneErrorCode err)
{
return static_cast<int>(err);
}
#endif // ERROR_H

View File

@@ -1,3 +1,4 @@
#include "error.h"
#include <QUrl>
#include <QtCore/QDir>
#include <QDebug>
@@ -14,7 +15,7 @@ extern "C" {
Git::Git():
m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1))),
m_ssh_homedir (QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation).append("/.ssh"))
QStandardPaths::AppDataLocation).append("/.ssh"))
{
qDebug() << "[Git] SSH Home is " << m_ssh_homedir.absolutePath();
QDir m_ssh_homedir(this->m_ssh_homedir);
@@ -71,18 +72,19 @@ bool Git::cloneSshKey(QString url, QString path, QString passphrase)
return this->clone(url, path, mode);
}
void Git::cloneResult(const bool err)
void Git::cloneResult(const int err_code, const QString message)
{
if (err) {
emit cloneFailed(); // TODO error message
} else {
if (err_code == code_err(GitCloneErrorCode::Successful)) {
emit cloneSucceed();
} else {
emit cloneFailed(err_code, message);
}
this->m_sem->release();
}
bool Git::importSshKey(QUrl source_path, bool is_private){
bool Git::importSshKey(QUrl source_path, bool is_private)
{
auto destination_path = is_private ? this->privKeyPath() : this->pubKeyPath();
QFile source_file(source_path.toLocalFile());

View File

@@ -1,8 +1,8 @@
#ifndef GIT_H
#define GIT_H
#include "error.h"
#include "jobs/gitjob.h"
#include "qdebug.h"
#include <QUrl>
#include <QObject>
#include <QSemaphore>
@@ -28,10 +28,10 @@ private slots:
* process finishes. It emits the appropriate signal based on whether the clone operation succeeded
* or failed.
*
* @param err A boolean indicating whether an error occurred during cloning. `true` if the clone failed,
* `false` if it succeeded.
* @param err A err_code indicating whether an error occurred during cloning.
* @param err An error message.
*/
void cloneResult(const bool err);
void cloneResult(const int err_code, const QString message);
signals:
/**
@@ -44,9 +44,11 @@ signals:
/**
* @brief Signal emitted when the cloning operation fails.
*
* @param err_code The error code
* @param msg The deffautl error message from libgit
* This signal is emitted when an error occurs during the cloning operation.
*/
void cloneFailed();
void cloneFailed(int err_code, QString msg);
private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */

View File

@@ -3,12 +3,14 @@
#include <QUrl>
#include <QDebug>
#include <QObject>
#include <memory>
#include <type_traits>
extern "C" {
#include <git2.h>
}
#include "clonejob.h"
#include "../error.h"
CloneJob::CloneJob(QString url, QString path, cred_type cred):
GitJob(cred),
@@ -18,69 +20,92 @@ CloneJob::CloneJob(QString url, QString path, cred_type cred):
this->setObjectName("CloneJob");
}
void CloneJob::run()
{
auto tmp_dir = this->cloneSetup();
auto ret = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB);
if (ret) {
const auto [errorCode, errorMessage] = this->clone(m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB);
if (errorCode == GitCloneErrorCode::Successful) {
this->moveToDestination(tmp_dir, this->m_path);
}
this->cloneCleanUp(tmp_dir);
emit resultReady(!ret); // TODO Clean error handling to return specifics errors for the ui
emit resultReady(code_err(errorCode), errorMessage);
}
QDir CloneJob::cloneSetup()
{
QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone"));
QDir tmp_dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation).append("/clone"));
tmp_dir.removeRecursively();
qDebug() << "[CloneJob] Temp dir path is " << tmp_dir.absolutePath();
tmp_dir.removeRecursively(); // Clean the directory before use
qDebug() << "[CloneJob] Temp dir path is" << tmp_dir.absolutePath();
return tmp_dir;
}
bool CloneJob::cloneCleanUp(QDir tmp_dir)
{
return tmp_dir.removeRecursively();
if (!tmp_dir.removeRecursively()) {
qWarning() << "[CloneJob] Failed to clean up temporary directory:" << tmp_dir.absolutePath();
return false;
}
return true;
}
bool CloneJob::moveToDestination(QDir tmp_dir, QString path)
bool CloneJob::moveToDestination(QDir tmp_dir, const QString& path)
{
qDebug() << "[CloneJob] Removing password_store " << path;
qDebug() << "[CloneJob] Removing existing destination:" << path;
QDir destination_dir(path);
destination_dir.removeRecursively();
destination_dir.removeRecursively(); // Clean the destination directory
qDebug() << "[CloneJob] Moving cloned content to destination dir";
QDir dir;
qDebug() << "[CloneJob]" << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath();
return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling
if (!QDir().rename(tmp_dir.absolutePath(), destination_dir.absolutePath())) {
qWarning() << "[CloneJob] Failed to move directory from" << tmp_dir.absolutePath() << "to" <<
destination_dir.absolutePath();
return false;
}
return true;
}
bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb)
const QPair<GitCloneErrorCode, QString> CloneJob::clone(QString url, QString path, cred_type cred,
git_cred_acquire_cb cb)
{
git_repository *repo = NULL;
git_repository *repo = nullptr; // Use nullptr for type safety
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
PayloadCB payload = PayloadCB(false, cred);
PayloadCB payload(false, cred);
opts.fetch_opts.callbacks.credentials = cb;
opts.fetch_opts.callbacks.payload = &payload;
int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts);
if (ret == GIT_EUSER ) {
qDebug() << "[CloneJob] CallBack Error";
} else if (ret != 0) {
auto err = git_error_last(); // TODO Better error handling for return ui messages
if (err) {
qDebug() << "[CloneJob]" << git_error_last()->message;
}
// Map the application specific cb errors if any
if (ret == GIT_EUSER) {
if (payload.err == ErrorCodeCB::NoUsername)
return {GitCloneErrorCode::NoUsername, "no username provided in URL"};
if (payload.err == ErrorCodeCB::InvalidCreds)
return {GitCloneErrorCode::AuthentificationError, "authentification error"};
if (payload.err == ErrorCodeCB::UrlTypeDoNotMatchCreds)
return {GitCloneErrorCode::UrlTypeDoNotMatchCreds, "invalid creds types for provided url"};
return {GitCloneErrorCode::UnexpectedError, "unexcepted error occured"};
}
const git_error* err = git_error_last(); // Retrieve the last error git error
// Log error details for debugging
if (err) {
qDebug() << "[CloneJob] Error class:" << err->klass;
qDebug() << "[CloneJob] Error message:" << err->message;
}
// Check if the repository was successfully created and free it
if (repo) {
git_repository_free(repo);
}
return ret == 0;
}
// Return the error code mapped from git_error_last
return { gitErrorToGitCloneErrorCode(err), err ? QString::fromUtf8(err->message) : "success"};
}

View File

@@ -6,6 +6,7 @@ extern "C" {
#include <git2.h>
}
#include "gitjob.h"
#include "../error.h"
/**
* @class CloneJob
@@ -26,14 +27,13 @@ class CloneJob : public GitJob
signals:
/**
* @brief Signal emitted when the cloning operation is complete.
*
* 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.
*/
void resultReady(const bool err);
* @brief Signal emitted when the cloning operation is complete.
*
* This signal is emitted once the cloning operation finishes.
*
* @param err A Git error.
*/
void resultReady(const int err_code, const QString message);
private:
QString m_url; ///< The URL of the Git repository to clone.
@@ -58,7 +58,7 @@ private:
* @param tmp_dir The temporary directory where the repository was cloned.
* @return `true` if the move was successful, `false` otherwise.
*/
static bool moveToDestination(QDir tmp_dir, QString path);
static bool moveToDestination(QDir tmp_dir, const QString& path);
/**
* @brief Tears down the temporary directory after cloning.
@@ -84,9 +84,10 @@ private:
* @param path The destination path for the cloned repository.
* @param cred The credentials to use for the cloning operation.
* @param cb The callback function for acquiring credentials during cloning.
* @return `true` if the cloning process was successful, `false` otherwise.
* @return A Git error, result of the operation.
*/
static bool clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb);
static const QPair<GitCloneErrorCode, QString> clone(QString url, QString path, cred_type cred, git_cred_acquire_cb cb);
public:
/**

View File

@@ -28,11 +28,13 @@ int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_
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";
p->err = ErrorCodeCB::NoUsername;
return (int) GIT_EUSER;
}
if (p->called) {
qWarning() << "[GitJob] credentials_cb : cb already called, probably invalid creds";
p->err = ErrorCodeCB::InvalidCreds;
return (int) GIT_EUSER;
}
p->called = true;
@@ -45,26 +47,24 @@ int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_
qWarning() << "[GitJob] credentialsCB : callback should never be call for HTTP";
return (int) GIT_EUSER;
},
[allowed_types, &out, &username_from_url](const HTTPUserPass & x)
[allowed_types, &out, &username_from_url, &p](const HTTPUserPass & x)
{
qDebug() << "[GitJob] credentialsCB : HTTPUserPass ";
if (!(allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)) {
qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass creds";
p->err = ErrorCodeCB::UrlTypeDoNotMatchCreds;
return (int) GIT_EUSER;
}
return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData());
},
[allowed_types, &out, &username_from_url](const SSHKey & x)
[allowed_types, &out, &username_from_url, &p](const SSHKey & x)
{
qDebug() << "[GitJob] credentialsCB : SSHKey ";
if (!(allowed_types & GIT_CREDTYPE_SSH_KEY)) {
qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for SSHKey creds";
p->err = ErrorCodeCB::UrlTypeDoNotMatchCreds;
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());
}

View File

@@ -31,12 +31,19 @@ struct SSHKey {
*/
typedef std::variant<HTTP, HTTPUserPass, SSHKey> cred_type;
enum class ErrorCodeCB {
None = 0,
NoUsername,
UrlTypeDoNotMatchCreds,
InvalidCreds,
};
struct PayloadCB
{
struct PayloadCB {
bool called;
cred_type creds;
PayloadCB(bool ca, cred_type cr): called(ca), creds(cr) {}
ErrorCodeCB err;
PayloadCB(bool ca, cred_type cr): called(ca), creds(cr), err(ErrorCodeCB::None) {}
};
/**

View File

@@ -1,7 +1,7 @@
#ifndef UTILS_H
#define UTILS_H
#define UNUSED(x) (void)(x)
#define UNUSED(x) (void)(x) // Stub used to hide some warnings
/**
* @brief A utility structure for enabling function overloading with template-based classes.

View File

@@ -6,66 +6,58 @@ extern "C" {
#include "rnp/rnp_err.h"
}
enum class ErrorCodeShow {
Success= 0,
UnexceptedError,
BadPassphrase,
NoKeyFound,
DecryptFailed
// Enum for general error codes
enum class ErrorCode {
Error = 1, ///< Generic error code
};
int rnpErrorToErrorCodeShow(int rnpErrorCode) {
// Enum for errors related to showing errors (e.g., password issues, key issues)
enum class ErrorCodeShow {
UnexpectedError = 1, ///< Unknown or unexpected error
BadPassphrase, ///< Invalid passphrase error
NoKeyFound, ///< Key not found error
DecryptFailed ///< Decryption failure error
};
/**
* Convert an RNP error code to a corresponding ErrorCodeShow
* @param rnpErrorCode The RNP error code
* @return Corresponding ErrorCodeShow integer value
*/
inline ErrorCodeShow rnpErrorToErrorCodeShow(int rnpErrorCode)
{
switch (rnpErrorCode) {
case RNP_SUCCESS:
return static_cast<int>(ErrorCodeShow::Success);
case RNP_ERROR_BAD_PASSWORD:
return static_cast<int>(ErrorCodeShow::BadPassphrase);
return ErrorCodeShow::BadPassphrase; ///< Bad passphrase error
case RNP_ERROR_KEY_NOT_FOUND:
case RNP_ERROR_NO_SUITABLE_KEY:
return static_cast<int>(ErrorCodeShow::NoKeyFound);
return ErrorCodeShow::NoKeyFound; ///< No key found error
case RNP_ERROR_DECRYPT_FAILED:
return static_cast<int>(ErrorCodeShow::DecryptFailed);
return ErrorCodeShow::DecryptFailed; ///< Decryption failure error
default:
return static_cast<int>(ErrorCodeShow::UnexceptedError);
return ErrorCodeShow::UnexpectedError; ///< Default to unexpected error
}
}
// Enum for errors related to importing key files
enum class ErrorCodeImportKeyFile {
Success= 0,
UnexceptedError,
BadFormat,
UnexpectedError = 1, ///< Unknown or unexpected error
BadFormat, ///< Bad format error when importing the key file
};
int rnpErrorToErrorCodeImportKeyFile(int rnpErrorCode) {
switch (rnpErrorCode) {
case RNP_SUCCESS:
return static_cast<int>(ErrorCodeShow::Success);
case RNP_ERROR_BAD_FORMAT:
return static_cast<int>(ErrorCodeImportKeyFile::BadFormat);
default:
return static_cast<int>(ErrorCodeImportKeyFile::UnexceptedError);
}
}
enum class ErrorCodeUnexvepted {
Success= 0,
UnexceptedError,
};
int rnpErrorToErrorCodeGeneric(int rnpErrorCode) {
switch (rnpErrorCode) {
case RNP_SUCCESS:
return static_cast<int>(ErrorCodeShow::Success);
default:
return static_cast<int>(ErrorCodeImportKeyFile::UnexceptedError);
}
}
enum class ErrorCode
/**
* Convert an RNP error code to a corresponding ErrorCodeImportKeyFile
* @param rnpErrorCode The RNP error code
* @return Corresponding ErrorCodeImportKeyFile integer value
*/
inline ErrorCodeImportKeyFile rnpErrorToErrorCodeImportKeyFile(int rnpErrorCode)
{
Success= 0,
Error,
};
switch (rnpErrorCode) {
case RNP_ERROR_BAD_FORMAT:
return ErrorCodeImportKeyFile::BadFormat; ///< Bad format error
default:
return ErrorCodeImportKeyFile::UnexpectedError; ///< Default to unexpected error
}
}
#endif // ERROR_H

View File

@@ -109,7 +109,7 @@ bool Pass::show(QUrl url)
void Pass::slotShowError(rnp_result_t err)
{
qInfo() << "[Pass] Show Failed";
emit showFailed(rnpErrorToErrorCodeShow(err), rnp_result_to_string(err));
emit showFailed((int) rnpErrorToErrorCodeShow(err), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -137,7 +137,7 @@ bool Pass::deletePasswordStore()
}
void Pass::slotDeletePasswordStoreResult(bool err)
{
{
if (err) {
qInfo() << "[Pass] Delete Password Store Failed";
emit deletePasswordStoreFailed(static_cast<int>(ErrorCode::Error), "Failed to delete password store");
@@ -168,7 +168,7 @@ bool Pass::deleteGPGKey(PassKeyModel* key)
void Pass::slotDeleteGPGKeyError(rnp_result_t err)
{
qInfo() << "[Pass] Delete GPG key Failed";
emit deleteGPGKeyFailed(rnpErrorToErrorCodeGeneric(err), rnp_result_to_string(err));
emit deleteGPGKeyFailed(static_cast<int>(ErrorCode::Error), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -198,7 +198,7 @@ bool Pass::importGPGKey(QUrl url)
void Pass::slotImportGPGKeyError(rnp_result_t err)
{
qInfo() << "[Pass] Import GPG Key Failed";
emit importGPGKeyFailed(rnpErrorToErrorCodeImportKeyFile(err), rnp_result_to_string(err));
emit importGPGKeyFailed((int) rnpErrorToErrorCodeImportKeyFile(err), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -229,7 +229,7 @@ void Pass::slotGetAllGPGKeysError(rnp_result_t err)
{
qInfo() << "[Pass] Get all GPG Keys Failed";
this->m_keyring_model = nullptr;
emit getAllGPGKeysFailed(rnpErrorToErrorCodeGeneric(err), rnp_result_to_string(err));
emit getAllGPGKeysFailed(static_cast<int>(ErrorCode::Error), rnp_result_to_string(err));
this->m_sem->release(1);
}

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-14 10:08+0100\n"
"POT-Creation-Date: 2026-01-05 19:03+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -194,7 +194,7 @@ msgid "You're are about to delete<br>the current Password Store.<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:57
#: ../qml/pages/settings/ImportGitClone.qml:142
#: ../qml/pages/settings/ImportGitClone.qml:169
#: ../qml/pages/settings/ImportZip.qml:66
#: ../qml/pages/settings/InfoKeys.qml:174
msgid "Yes"
@@ -208,50 +208,71 @@ msgstr ""
msgid "Password Store deleted !"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:141
#: ../qml/pages/settings/ImportGitClone.qml:56
msgid "An unexpected error occurred during the clone operation."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:59
msgid "Invalid URL for the current clone operation."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:62
msgid "Username is missing in the URL."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:65
msgid "Authentication error, credentials may be invalid."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:68
msgid ""
"Credentials type does not match the URL for the current clone operation."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:168
msgid ""
"Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:155
#: ../qml/pages/settings/ImportGitClone.qml:182
msgid "An error occured during git clone !"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:164
#: ../qml/pages/settings/ImportGitClone.qml:192
#: ../qml/pages/settings/ImportZip.qml:88
msgid "Password store sucessfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:176
#: ../qml/pages/settings/ImportGitClone.qml:204
msgid "Git Clone Import"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:7
#: ../qml/pages/settings/ImportKeyFile.qml:9
msgid "GPG Key Import"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:8
#: ../qml/pages/settings/ImportKeyFile.qml:10
msgid "Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:9
#: ../qml/pages/settings/ImportKeyFile.qml:11
msgid "Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:33
#: ../qml/pages/settings/ImportKeyFile.qml:35
msgid "The file is not in a valid key format"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:10
#: ../qml/pages/settings/ImportSSHkey.qml:12
msgid "SSH Key Import"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:11
#: ../qml/pages/settings/ImportSSHkey.qml:13
msgid "SSH Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:12
#: ../qml/pages/settings/ImportSSHkey.qml:14
msgid "SSH Key import failed !"
msgstr ""

View File

@@ -1,18 +1,16 @@
import Git 1.0
import Lomiri.Components 1.3
import Pass 1.0
import Utils 1.0
import QtQuick 2.4
import Utils 1.0
Column {
property alias importSshPrivKeyButton : repoImportPrivKeyButton
property alias importSshPubKeyButton : repoImportPubKeyButton
property alias deleteSshPrivKeyButton : repoDeletePrivKeyButton
property alias deleteSshPubKeyButton : repoDeletePubKeyButton
property bool __sshPrivKeyAvailable : false
property bool __sshPubKeyAvailable : false
property alias importSshPrivKeyButton: repoImportPrivKeyButton
property alias importSshPubKeyButton: repoImportPubKeyButton
property alias deleteSshPrivKeyButton: repoDeletePrivKeyButton
property alias deleteSshPubKeyButton: repoDeletePubKeyButton
property bool __sshPrivKeyAvailable: false
property bool __sshPubKeyAvailable: false
signal repoUrlChanged(string url)
@@ -28,7 +26,6 @@ Column {
Component.onCompleted: {
update();
}
anchors.top: parent.fill
spacing: units.gu(1)
@@ -108,7 +105,6 @@ Column {
visible: !__sshPrivKeyAvailable
}
Text {
id: repoPassphraseLabel

View File

@@ -1,4 +1,3 @@
import "../dialogs"
import "../pages/headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
@@ -11,17 +10,15 @@ Page {
id: importKeyFilePage
property var activeTransfer
property alias contentPicker : contentPicker
property alias dialogImportKeyPageError : dialogImportKeyPageError
property alias dialogImportKeyPageSucess : dialogImportKeyPageSucess
property string headerTitle : i18n.tr("Import succeeded !")
property string dialogErrorTxt : i18n.tr("Import failed !")
property string dialogErrorDescriptionTxt : null
property string dialogSuccessTxt : i18n.tr("File Imported")
property alias contentPicker: contentPicker
property string headerTitle: i18n.tr("Import succeeded !")
property string dialogErrorTxt: i18n.tr("Import failed !")
property string dialogErrorDescriptionTxt: null
property string dialogSuccessTxt: i18n.tr("File Imported")
ContentPeerPicker {
id: contentPicker
anchors.top: importKeyHeader.bottom
anchors.bottom: parent.bottom
anchors.topMargin: importKeyFilePage.header.height
@@ -42,28 +39,6 @@ Page {
activeTransfer: importKeyFilePage.activeTransfer
}
Component {
id: dialogImportKeyPageError
ErrorDialog {
textError: importKeyFilePage.dialogErrorTxt
textErrorDescription: importKeyFilePage.dialogErrorDescriptionTxt
}
}
Component {
id: dialogImportKeyPageSucess
SuccessDialog {
textSuccess: importKeyFilePage.dialogSuccessTxt
onDialogClosed: {
pageStack.pop();
}
}
}
header: StackHeader {
id: importKeyHeader

View File

@@ -6,12 +6,12 @@ Dialog {
id: dialog
property string textError
property string textErrorDescription : null
property string textErrorDescription: null
signal dialogClosed()
title: i18n.tr("Error !")
text: textErrorDescription ? (textError + "<br>" + textErrorDescription) : textError
text: textErrorDescription ? (textError + "<br>" + textErrorDescription) : textError
Button {
text: i18n.tr("Close")

View File

@@ -51,22 +51,26 @@ Page {
});
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;
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);
});
@@ -195,10 +199,9 @@ Page {
textError: i18n.tr("Decryption failed !")
textErrorDescription: __text_error_description
}
}
Timer {
id: searchTimer

View File

@@ -9,27 +9,35 @@ import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
Page {
// property int __gitModeSSH_KEY : 2
id: importGitClonePage
property int __gitModeHTTP : 0
property int __gitModeHTTP_AUTH : 1
property int __gitModeSSH_KEY : 2
property int __gitModeHTTP: 0
property int __gitModeHTTP_AUTH: 1
property int __gitCloneErrorCodeSuccessful: 0
property int __gitCloneErrorCodeUnexpectedError: 1
property int __gitCloneErrorCodeInvalidUrl: 2
property int __gitCloneErrorCodeNoUsername: 3
property int __gitCloneErrorCodeAuthentificationError: 4
property int __gitCloneErrorCodeUrlTypeDoNotMatchCreds: 5
property string __repoUrl
property string __err_message: null
function __loadForm() {
// case __gitModeSSH_KEY:
// importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneSshKey.qml");
// break;
switch (combo.selectedIndex) {
case __gitModeHTTP:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttp.qml");
break;
case __gitModeHTTP_AUTH:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttpAuth.qml");
break;
case __gitModeSSH_KEY:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneSshKey.qml");
break;
case __gitModeHTTP:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttp.qml");
break;
case __gitModeHTTP_AUTH:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttpAuth.qml");
break;
}
}
@@ -37,14 +45,35 @@ Page {
Git.cloneSucceed.connect(function() {
GitSettings.type = combo.selectedIndex;
GitSettings.repoUrl = importGitClonePage.__repoUrl;
if(GitSettings.type != __gitModeSSH_KEY) { // ensure there no ssh key is kept if swicthing to another git mode
if (GitSettings.type != __gitModeSSH_KEY) {
// ensure there no ssh key is kept if swicthing to another git mode
Utils.rmFile(Git.privKey);
Utils.rmFile(Git.pubKey);
}
PopupUtils.open(dialogGitCloneSuccess);
PopupUtils.open(dialogGitCloneSuccess, importGitClonePage);
});
Git.cloneFailed.connect(function() {
PopupUtils.open(dialogGitCloneError);
Git.cloneFailed.connect(function(err_code, msg) {
switch (err_code) {
case __gitCloneErrorCodeUnexpectedError:
__err_message = i18n.tr("An error occurred during the clone operation.");
break;
case __gitCloneErrorCodeInvalidUrl:
__err_message = i18n.tr("Invalid URL for the current clone operation.");
break;
case __gitCloneErrorCodeNoUsername:
__err_message = i18n.tr("Username is missing in the URL.");
break;
case __gitCloneErrorCodeAuthentificationError:
__err_message = i18n.tr("Authentication error, credentials may be invalid.");
break;
case __gitCloneErrorCodeUrlTypeDoNotMatchCreds:
__err_message = i18n.tr("Credentials type does not match the URL for the current clone operation.");
break;
default:
__err_message = msg;
break;
}
PopupUtils.open(dialogGitCloneError, importGitClonePage);
});
if (GitSettings.repoUrl)
__repoUrl = GitSettings.repoUrl;
@@ -76,7 +105,7 @@ Page {
id: combo
width: parent.width
model: ["HTTP", "HTTP AUTH", "SSH KEY"]
model: ["HTTP", "HTTP AUTH"] //, "SSH KEY"]
onDelegateClicked: function(i) {
timer.setTimeout(function() {
__loadForm();
@@ -106,29 +135,29 @@ Page {
width: parent.width
onLoaded: {
// case __gitModeSSH_KEY:
// importGitCloneForm.item.importSshPrivKeyButton.clicked.connect(function() {
// pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
// "isPrivateKey": true
// });
// });
// importGitCloneForm.item.importSshPubKeyButton.clicked.connect(function() {
// pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
// "isPrivateKey": false
// });
// });
// break;
importGitCloneForm.item.repoUrlChanged.connect(function(url) {
importGitClonePage.__repoUrl = url;
});
importGitCloneForm.item.setRepoUrl(importGitClonePage.__repoUrl);
switch (combo.selectedIndex) {
case __gitModeHTTP:
break;
case __gitModeHTTP_AUTH:
break;
case __gitModeSSH_KEY:
importGitCloneForm.item.importSshPrivKeyButton.clicked.connect(function() {
pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
"isPrivateKey": true
});
});
importGitCloneForm.item.importSshPubKeyButton.clicked.connect(function() {
pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
"isPrivateKey": false
});
});
break;
}
case __gitModeHTTP:
break;
case __gitModeHTTP_AUTH:
break;
}
}
}
@@ -153,6 +182,7 @@ Page {
ErrorDialog {
textError: i18n.tr("An error occured during git clone !")
textErrorDescription: __err_message
}
}

View File

@@ -1,46 +1,105 @@
import "../../components"
import "../../dialogs"
import "../headers"
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Content 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
ImportFile {
Page {
id: importKeyFilePage
headerTitle: i18n.tr("GPG Key Import")
dialogSuccessTxt : i18n.tr("Key successfully imported !")
dialogErrorTxt : i18n.tr("Key import failed !")
property var activeTransfer
property alias contentPicker: contentPicker
property string __text_error_description: null
contentPicker.onPeerSelected: {
{
peer.selectionType = ContentTransfer.Single;
importKeyFilePage.activeTransfer = peer.request();
importKeyFilePage.activeTransfer.stateChanged.connect(function() {
if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKey(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKeySucceed.connect(function() {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
PopupUtils.open(importKeyFilePage.dialogImportKeyPageSucess);
});
Pass.importGPGKeyFailed.connect(function(err, message) {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
switch (code) {
case 1: // UnexceptedError -> use the default (not translate) rnp error
__text_error_description = message;
break;
case 2: // BadFormat
ContentPeerPicker {
id: contentPicker
anchors.top: importKeyHeader.bottom
anchors.bottom: parent.bottom
anchors.topMargin: importKeyFilePage.header.height
width: parent.width
visible: parent.visible
showTitle: false
contentType: ContentType.Text
handler: ContentHandler.Source
onPeerSelected: {
{
importKeyFilePage.contentPicker.peer.selectionType = ContentTransfer.Single;
importKeyFilePage.activeTransfer = importKeyFilePage.contentPicker.peer.request();
importKeyFilePage.activeTransfer.stateChanged.connect(function() {
if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKey(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKeySucceed.connect(function() {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
PopupUtils.open(dialogImportKeyPageSucess);
});
Pass.importGPGKeyFailed.connect(function(err, message) {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
switch (code) {
case 1:
// UnexceptedError -> use the default (not translate) rnp error
__text_error_description = message;
break;
case 2:
// BadFormat
__text_error_description = i18n.tr("The file is not in a valid key format");
break;
default:
console.warn("Unhandled error code");
__text_error_description = message;
break;
}
PopupUtils.open(importKeyFilePage.dialogImportKeyPageError);
});
}
});
}
break;
default:
console.warn("Unhandled error code");
__text_error_description = message;
break;
}
PopupUtils.open(dialogImportKeyPageError);
});
}
});
};
}
onCancelPressed: {
pageStack.pop();
}
}
ContentTransferHint {
id: transferHint
anchors.fill: parent
activeTransfer: importKeyFilePage.activeTransfer
}
Component {
id: dialogImportKeyPageError
ErrorDialog {
textError: i18n.tr("Key import failed !")
textErrorDescription: __text_error_description
}
}
Component {
id: dialogImportKeyPageSucess
SuccessDialog {
textSuccess: i18n.tr("Key successfully imported !")
onDialogClosed: {
pageStack.pop();
}
}
}
header: StackHeader {
id: importKeyHeader
title: i18n.tr("GPG Key Import")
}
}

View File

@@ -1,6 +1,9 @@
import "../../components"
import Utils 1.0
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Content 1.3
import Utils 1.0
ImportFile {
id: importSSHKeyPage
@@ -8,27 +11,25 @@ ImportFile {
property bool isPrivateKey
headerTitle: i18n.tr("SSH Key Import")
dialogSuccessTxt : i18n.tr("SSH Key successfully imported !")
dialogErrorTxt : i18n.tr("SSH Key import failed !")
dialogSuccessTxt: i18n.tr("SSH Key successfully imported !")
dialogErrorTxt: i18n.tr("SSH Key import failed !")
contentPicker.onPeerSelected: {
{
peer.selectionType = ContentTransfer.Single;
importSSHKeyPage.activeTransfer = peer.request();
importSSHKeyPage.activeTransfer.stateChanged.connect(function() {
if (importSSHKeyPage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importSSHKeyPage.activeTransfer.items[0].url);
var ret = Git.importSshKey(importSSHKeyPage.activeTransfer.items[0].url, isPrivateKey);
Utils.rmFile(importSSHKeyPage.activeTransfer.items[0].url);
importSSHKeyPage.activeTransfer = null;
if(ret) {
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageSucess);
} else {
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageError);
}
}
});
}
{
importSSHKeyPage.contentPicker.peer.selectionType = ContentTransfer.Single;
importSSHKeyPage.activeTransfer = importSSHKeyPage.contentPicker.peer.request();
importSSHKeyPage.activeTransfer.stateChanged.connect(function() {
if (importSSHKeyPage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importSSHKeyPage.activeTransfer.items[0].url);
var ret = Git.importSshKey(importSSHKeyPage.activeTransfer.items[0].url, isPrivateKey);
Utils.rmFile(importSSHKeyPage.activeTransfer.items[0].url);
importSSHKeyPage.activeTransfer = null;
if (ret)
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageSucess);
else
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageError);
}
});
};
}
}

View File

@@ -195,7 +195,7 @@ Page {
SuccessDialog {
textSuccess: i18n.tr("Key successfully deleted !")
onDialogClosed: {
infoKeysListView.model = Pass.getAllGPGKeys();
Pass.getAllGPGKeys();
}
}