1
0
mirror of https://github.com/QRouland/UTPass.git synced 2025-07-01 01:32:29 +00:00

26 Commits

Author SHA1 Message Date
2be75f9bd6 Fix typo 2025-03-14 10:09:20 +01:00
dc2c35ca9b Improve Lib Pass Error Messages 2025-03-14 10:07:05 +01:00
bdb2d58ac4 Some improvements 2025-03-12 15:34:33 +01:00
884488b9ed Add support for ssh clone 2025-02-21 15:50:27 +01:00
5683db69c7 Update README 2025-02-18 16:04:47 +01:00
6306af6dde Add initial ssh support in git cpp plugin 2025-02-18 14:20:35 +01:00
ec4ebca950 Merge pull request #4 from Vistaus/master
Added Dutch translation
2025-02-17 07:41:10 +00:00
cf89bd04bb Added Dutch translation 2025-02-12 22:21:11 +01:00
7e8ac60cc9 Some clean up 2025-02-11 10:52:11 +01:00
c6f2424017 Fix issue where textfield used wrong signals 2025-02-07 14:17:14 +01:00
6fe50d2c90 Save last git clone settings (urls,types) 2025-02-07 13:38:09 +01:00
2409f33f59 Add search password feature (equivalent to pass find) 2025-02-05 15:07:38 +01:00
e47d50072a Fix style command to not include third party libs 2025-02-05 15:07:38 +01:00
197a12a570 Typos Changelog 2025-02-05 15:07:31 +01:00
50441e0daf Typos Changelog 2025-02-04 18:21:35 +01:00
6c93aa3d62 Bump version 0.0.4-dev 2025-02-04 18:20:41 +01:00
0cf07b1b7a Bump version 0.0.3 2025-02-04 17:51:14 +01:00
6b5d9fb8e2 Fix #2 Password names containing periods are truncated 2025-02-04 17:44:42 +01:00
c106bbec19 Fix main page not reloading on imports 2025-02-04 17:27:01 +01:00
bcc6d7c316 Update Readme 2025-02-04 17:25:37 +01:00
fc02000b89 Fix decrypt issue 2025-02-04 16:36:00 +01:00
72a3a8fbcc Fix password page color issues with dark theme 2025-02-04 16:17:28 +01:00
efb57dd70c Fix ui issues on imports 2025-02-04 15:51:47 +01:00
bef910bce3 Update readme for export with new git functionnality 2025-02-04 14:25:31 +01:00
bf7c2091b7 Fix bug causing crashes during git clone when no username is provided 2025-02-04 14:09:02 +01:00
5582b4dd70 Ensure that key and zip are deleted after import 2025-02-04 13:37:25 +01:00
53 changed files with 1573 additions and 537 deletions

24
CHANGELOG.md Normal file
View File

@ -0,0 +1,24 @@
# 0.0.3: Git Initial Support and Move to RNP
- Port app to Focal
- Improve UI :
* Follow human interface guidelines
* Fix various components color to work with dark theme
- Rewrite of Pass Plugin:
* Move from GPGMe to RNP for GPG operations due to issues running GPG agent in a confined app
* Improve multithreading code for GPG operations
- Add Git HTTP and HTTP AUTH clone for password store import feature
- Add delete password store feature
# 0.0.2 : Added translations
- 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
- Import of gpg keys via file
- Suppression of gpg keys
- Import of password via a password store zip
- Password decryption
- Password copy to clipboard

View File

@ -48,6 +48,10 @@ endif()
qt5_use_modules(${PROJECT_NAME} Gui Qml Quick QuickTest) qt5_use_modules(${PROJECT_NAME} Gui Qml Quick QuickTest)
set_source_files_properties(qml/singletons/GitSettings.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(TESTS_RUNNER) if(TESTS_RUNNER)
qt5_use_modules(${PROJECT_NAME} QuickTest) qt5_use_modules(${PROJECT_NAME} QuickTest)
endif() endif()

View File

@ -4,7 +4,7 @@ A Ubuntu Touch password management app aiming to be compatible with [ZX2C4s p
## Installation ## Installation
UTPass is avalaible on the [OpenStore](open-store.io) 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) [![OpenStore](https://open-store.io/badges/en_US.png)](https://open-store.io/app/utpass.qrouland)
@ -20,22 +20,25 @@ Assuming that there are already passwords in another device using [ZX2C4s pas
Export gpg private keys in order to decrypt passwords: Export gpg private keys in order to decrypt passwords:
``` ```
gpg --output keys.gpg --export-secret-keys gpg --output keys.gpg --export-secret-keys <key>
``` ```
Export passwords, assuming they reside in *.password-store* folder: 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.
Export passwords to a ZIP archive, assuming they reside in the *.password-store* folder:
``` ```
zip passwords.zip -r .password-store/ zip passwords.zip -r .password-store/
``` ```
Both files have the correct format for UTPass to import them and work as intended. It is highly recommended to remove them after imported to **UTPass**. Files have the correct format for UTPass to import them and work as intended. It is highly recommended to remove them after imported to **UTPass**.
## Build & Tests ## Build & Tests
See [Build & Tests wiki page](https://github.com/QRouland/UTPass/wiki/Build-&-Tests) See [Build & Tests wiki page](https://github.com/QRouland/UTPass/wiki/Build-&-Tests)
## Contributing & Issues ## Contributing & Translation
See [Contributing wiki page](https://github.com/QRouland/UTPass/wiki/Contributing) See [Contributing wiki page](https://github.com/QRouland/UTPass/wiki/Contributing)
@ -44,11 +47,9 @@ See [Contributing wiki page](https://github.com/QRouland/UTPass/wiki/Contributin
Some useful links related to UTPass development : Some useful links related to UTPass development :
* [Ubports](https://ubports.com/) : Ubuntu Touch Community * [Ubports](https://ubports.com/) : Ubuntu Touch Community
* [ZX2C4s pass command line application](https://www.passwordstore.org/) : the standard unix password manager. * [ZX2C4s pass command line application](https://www.passwordstore.org/) : The standard unix password manager.
* [Clickable](https://github.com/bhdouglass/clickable) : Compile, build, and deploy Ubuntu Touch click packages * [Clickable](https://github.com/bhdouglass/clickable) : Compile, build, and deploy Ubuntu Touch click packages
* [GnuPG](https://gnupg.org/): The GNU Privacy Guard * [Rnp](https://github.com/rnpgp/rnp) : High performance C++ OpenPGP library used by Mozilla Thunderbird
* [Gpgme](https://www.gnupg.org/software/gpgme/index.html) : GnuPG Made Easy (GPGME) is a library designed to make access to GnuPG easier for applications
## License ## License

View File

@ -4,7 +4,7 @@ kill: UTPass
scripts: scripts:
style: >- style: >-
echo 'Running Astyle :' && astyle --options=.astylerc --recursive '*.cpp,*.h' --exclude=build && echo 'Running QmlFormat' && find . -name "*.qml" -exec qmlformat -i {} \; && echo 'Success' echo 'Running Astyle :' && astyle --options=.astylerc --recursive '*.cpp,*.h' --exclude=build --exclude=libs && echo 'Running QmlFormat' && find . -name "*.qml" -exec qmlformat -i {} \; && echo 'Success'
dependencies_target: dependencies_target:

View File

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

View File

@ -12,8 +12,15 @@ extern "C" {
#include "jobs/gitjob.h" #include "jobs/gitjob.h"
Git::Git(): Git::Git():
m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1))) m_sem(std::unique_ptr<QSemaphore>(new QSemaphore(1))),
m_ssh_homedir (QStandardPaths::writableLocation(
QStandardPaths::AppDataLocation).append("/.ssh"))
{ {
qDebug() << "[Git] SSH Home is " << m_ssh_homedir.absolutePath();
QDir m_ssh_homedir(this->m_ssh_homedir);
if (!m_ssh_homedir.exists()) {
m_ssh_homedir.mkpath(".");
}
git_libgit2_init(); git_libgit2_init();
} }
@ -26,16 +33,15 @@ Git::~Git()
bool Git::clone(QString url, QString path, cred_type mode) bool Git::clone(QString url, QString path, cred_type mode)
{ {
if (!this->m_sem->tryAcquire(1, 500)) { if (!this->m_sem->tryAcquire(1, 500)) {
qWarning() << "Can acquire git semaphore a command is already running "; qWarning() << "[Git] Can acquire git semaphore a command is already running ";
return false; return false;
} }
auto v = overload { auto v = overload {
[](const HTTP & x) { return "HTTP"; }, [](const HTTP & x) { UNUSED(x); return "HTTP"; },
[](const HTTPUserPass & x) { return "HTTPAuth"; }, [](const HTTPUserPass & x) { UNUSED(x); return "HTTPAuth"; },
[](const SSHPass & x) { return "SSHAuth"; }, [](const SSHKey & x) { UNUSED(x); return "SSHKey"; },
[](const SSHKey & 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); CloneJob *clone_job = new CloneJob(url, path, mode);
connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult); connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult);
connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater); connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater);
@ -45,18 +51,26 @@ bool Git::clone(QString url, QString path, cred_type mode)
bool Git::cloneHttp(QString url, QString path) bool Git::cloneHttp(QString url, QString path)
{ {
qInfo() << "Call clone command Http " << url << " " << path; qInfo() << "[Git] Call clone command Http " << url << " " << path;
HTTP mode = {}; HTTP mode = {};
return this->clone(url, path, mode); return this->clone(url, path, mode);
} }
bool Git::cloneHttpPass(QString url, QString path, QString pass) bool Git::cloneHttpPass(QString url, QString path, QString pass)
{ {
qInfo() << "Call clone command HttpPass " << url << " " << path; qInfo() << "[Git] Call clone command HttpPass " << url << " " << path;
HTTPUserPass mode = { pass }; HTTPUserPass mode = { pass };
return this->clone(url, path, mode); return this->clone(url, path, mode);
} }
bool Git::cloneSshKey(QString url, QString path, QString passphrase)
{
qInfo() << "[Git] Call clone command SshKey " << url << " " << path;
SSHKey mode = { this->pubKeyPath(), this->privKeyPath(), passphrase };
return this->clone(url, path, mode);
}
void Git::cloneResult(const bool err) void Git::cloneResult(const bool err)
{ {
@ -67,3 +81,23 @@ void Git::cloneResult(const bool err)
} }
this->m_sem->release(); this->m_sem->release();
} }
bool Git::importSshKey(QUrl source_path, bool is_private){
auto destination_path = is_private ? this->privKeyPath() : this->pubKeyPath();
QFile source_file(source_path.toLocalFile());
if (!source_file.exists()) {
qWarning() << "[Git] Source file does not exist.";
return false;
}
QDir target_dir = QFileInfo(destination_path).absoluteDir();
if (!target_dir.exists()) {
if (!target_dir.mkpath(".")) {
qWarning() << "[Git] Failed to create target directory.";
return false;
}
}
return source_file.copy(destination_path);
}

View File

@ -2,6 +2,7 @@
#define GIT_H #define GIT_H
#include "jobs/gitjob.h" #include "jobs/gitjob.h"
#include "qdebug.h"
#include <QUrl> #include <QUrl>
#include <QObject> #include <QObject>
#include <QSemaphore> #include <QSemaphore>
@ -16,6 +17,8 @@
class Git : public QObject class Git : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString privKey READ pubKeyPath)
Q_PROPERTY(QString pubKey READ privKeyPath)
private slots: private slots:
/** /**
@ -47,12 +50,13 @@ signals:
private: private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */ std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
QDir m_ssh_homedir; /**< Directory that contains the SSH keys (public and private). */
/** /**
* @brief Clones a repository from a specified URL. * @brief Clones a repository from a specified URL.
* *
* This private method initiates the cloning job. It sets up the repository cloning process based on * 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 url The URL of the Git repository to clone.
* @param path The destination path for the cloned repository. * @param path The destination path for the cloned repository.
@ -61,30 +65,53 @@ private:
*/ */
bool clone(QString url, QString path, cred_type mode); bool clone(QString url, QString path, cred_type mode);
protected:
/**
* @brief Get the path to the public keyring.
*
* @return The file path to the public key.
*/
QString pubKeyPath()
{
return this->m_ssh_homedir.filePath("id_rsa.pub");
}
/**
* @brief Get the path to the secret keyring.
*
* @return The file path to the private key.
*/
QString privKeyPath()
{
return this->m_ssh_homedir.filePath("id_rsa");
}
public: public:
/** /**
* @brief Constructor for the Git class. * @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(); Git();
/** /**
* @brief Destructor for the Git class. * @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; ~Git() override;
Q_INVOKABLE bool importSshKey(QUrl source_path, bool is_private);
/** /**
* @brief Clones a repository over HTTP. * @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. * 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 url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository. * @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); Q_INVOKABLE bool cloneHttp(QString url, QString path);
@ -97,13 +124,22 @@ public:
* @param url The HTTP URL of the Git repository to clone. * @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository. * @param path The destination path for the cloned repository.
* @param pass The password used for HTTP authentication. * @param pass The password used for HTTP authentication.
* @return `true` if the clone operation was successful, `false` otherwise. * @return `true` if the clone job operation was successfully started, `false` otherwise.
*/ */
Q_INVOKABLE bool cloneHttpPass(QString url, QString path, QString pass); Q_INVOKABLE bool cloneHttpPass(QString url, QString path, QString pass);
// Future SSH support methods: /**
// Q_INVOKABLE bool clone_ssh_pass(QString url, QString path, QString pass); * @brief Clones a repository over SSH with a key for authentication.
// Q_INVOKABLE bool clone_ssh_key(QString url, QString path, QString pub_key, QString priv_key, QString passphrase); *
* This method clones a Git repository from the specified ssh URL using the provided password for authentication,
* and saves it to the given destination path.
*
* @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @param passphrase The passphrase used for SSH authentication.
* @return `true` if the clone job operation was successfully started, `false` otherwise.
*/
Q_INVOKABLE bool cloneSshKey(QString url, QString path, QString passphrase);
// Q_INVOKABLE bool update(QUrl url, QString path); // Q_INVOKABLE bool update(QUrl url, QString path);
// .... // ....

View File

@ -22,13 +22,13 @@ CloneJob::CloneJob(QString url, QString path, cred_type cred):
void CloneJob::run() void CloneJob::run()
{ {
auto tmp_dir = this->cloneSetup(); auto tmp_dir = this->cloneSetup();
auto err = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB); auto ret = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB);
if (!err) { if (ret) {
this->moveToDestination(tmp_dir, this->m_path); this->moveToDestination(tmp_dir, this->m_path);
} }
this->cloneTearDown(tmp_dir); this->cloneCleanUp(tmp_dir);
emit resultReady(err); // TODO Clean error handling to return specifics errors for the ui emit resultReady(!ret); // TODO Clean error handling to return specifics errors for the ui
} }
@ -37,26 +37,26 @@ QDir CloneJob::cloneSetup()
QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone")); QDir tmp_dir(QStandardPaths::writableLocation( QStandardPaths::CacheLocation).append("/clone"));
tmp_dir.removeRecursively(); tmp_dir.removeRecursively();
qDebug() << "Temp dir path is " << tmp_dir.absolutePath(); qDebug() << "[CloneJob] Temp dir path is " << tmp_dir.absolutePath();
return tmp_dir; return tmp_dir;
} }
bool CloneJob::cloneTearDown(QDir tmp_dir) bool CloneJob::cloneCleanUp(QDir tmp_dir)
{ {
return tmp_dir.removeRecursively(); return tmp_dir.removeRecursively();
} }
bool CloneJob::moveToDestination(QDir tmp_dir, QString path) bool CloneJob::moveToDestination(QDir tmp_dir, QString path)
{ {
qDebug() << "Removing password_store " << path; qDebug() << "[CloneJob] Removing password_store " << path;
QDir destination_dir(path); QDir destination_dir(path);
destination_dir.removeRecursively(); destination_dir.removeRecursively();
qDebug() << "Moving cloned content to destination dir"; qDebug() << "[CloneJob] Moving cloned content to destination dir";
QDir dir; QDir dir;
qDebug() << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath(); qDebug() << "[CloneJob]" << tmp_dir.absolutePath() << " to " << destination_dir.absolutePath();
return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling return dir.rename(tmp_dir.absolutePath(), destination_dir.absolutePath()); // TODO Better error handling
} }
@ -64,17 +64,23 @@ bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire
{ {
git_repository *repo = NULL; git_repository *repo = NULL;
git_clone_options opts = GIT_CLONE_OPTIONS_INIT; git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
PayloadCB payload = PayloadCB(false, cred);
opts.fetch_opts.callbacks.credentials = cb; 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); int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts);
if (ret != 0) { if (ret == GIT_EUSER ) {
qDebug() << git_error_last()->message; 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;
}
} }
if (repo) { if (repo) {
git_repository_free(repo); git_repository_free(repo);
}// TODO Better error handling }
return ret != 0; return ret == 0;
} }

View File

@ -70,7 +70,7 @@ private:
* @param tmp_dir The temporary directory to tear down. * @param tmp_dir The temporary directory to tear down.
* @return `true` if the teardown was successful, `false` otherwise. * @return `true` if the teardown was successful, `false` otherwise.
*/ */
static bool cloneTearDown(QDir tmp_dir); static bool cloneCleanUp(QDir tmp_dir);
/** /**
* @brief Clones a repository from a specified URL. * @brief Clones a repository from a specified URL.

View File

@ -18,36 +18,57 @@ GitJob::~GitJob()
git_libgit2_shutdown(); git_libgit2_shutdown();
} }
int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_from_url, int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_from_url,
unsigned int allowed_types, void *payload) unsigned int allowed_types, void *payload)
{ {
cred_type *cred = (cred_type *)payload; UNUSED(url);
PayloadCB *p = (PayloadCB *)payload;
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 { auto v = overload {
[](const HTTP & x) [](const HTTP & x)
{ {
qDebug() << "credentialsCB : HTTP "; UNUSED(x);
qWarning() << "credentialsCB : callback should never be call for HTTP "; qDebug() << "[GitJob] credentialsCB : HTTP ";
qWarning() << "[GitJob] credentialsCB : callback should never be call for HTTP";
return (int) GIT_EUSER; return (int) GIT_EUSER;
}, },
[&out, &username_from_url](const HTTPUserPass & x) [allowed_types, &out, &username_from_url](const HTTPUserPass & x)
{ {
qDebug() << "credentialsCB : HTTPUserPass "; qDebug() << "[GitJob] credentialsCB : HTTPUserPass ";
if (!username_from_url) { if (!(allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)) {
qWarning() << "credentials_cb : no username provided "; qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass creds";
return (int) GIT_EUSER; return (int) GIT_EUSER;
} }
return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData()); return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData());
}, },
[](const SSHPass & x) [allowed_types, &out, &username_from_url](const SSHKey & x)
{ {
qWarning() << "credentials_cb : SSHAuth to be implemented "; qDebug() << "[GitJob] credentialsCB : SSHKey ";
return (int) GIT_EUSER; if (!(allowed_types & GIT_CREDTYPE_SSH_KEY)) {
}, // TODO qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for SSHKey creds";
[](const SSHKey & x) return (int) GIT_EUSER;
{ }
qWarning() << "credentials_cb : SSHKey to be implemented "; qDebug() << "[GitJob] username_from_url :" << username_from_url;
return (int) GIT_EUSER; qDebug() << "[GitJob] pub_key :" << x.pub_key.toLocal8Bit().constData();
} // TODO 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());
}
}; };
return std::visit(v, *cred); auto ret = std::visit(v, p->creds);
return ret;
} }

View File

@ -1,6 +1,7 @@
#ifndef GITJOB_H #ifndef GITJOB_H
#define GITJOB_H #define GITJOB_H
#include <QDir>
#include <QThread> #include <QThread>
extern "C" { extern "C" {
#include <git2.h> #include <git2.h>
@ -8,12 +9,17 @@ extern "C" {
#include <variant> #include <variant>
// Forward declarations for the different credential types. // Forward declarations for the different credential types.
struct HTTP { }; struct HTTP {};
struct HTTPUserPass { struct HTTPUserPass {
QString pass; ///< Password for HTTP user authentication. QString pass; ///< Password for HTTP user authentication.
}; };
struct SSHPass { }; struct SSHKey {
struct SSHKey { }; QString pub_key; ///< public key path.
QString priv_key; ///< private key path.
QString passphrase; ///< key passphrase.
};
/** /**
* @brief Variant type to represent various types of credentials. * @brief Variant type to represent various types of credentials.
@ -21,10 +27,17 @@ struct SSHKey { };
* This type is used to store one of the following credential types: * This type is used to store one of the following credential types:
* - HTTP * - HTTP
* - HTTPUserPass * - HTTPUserPass
* - SSHPass
* - SSHKey * - SSHKey
*/ */
typedef std::variant<HTTP, HTTPUserPass, SSHPass, SSHKey> cred_type; typedef std::variant<HTTP, HTTPUserPass, SSHKey> cred_type;
struct PayloadCB
{
bool called;
cred_type creds;
PayloadCB(bool ca, cred_type cr): called(ca), creds(cr) {}
};
/** /**
* @class GitJob * @class GitJob

View File

@ -9,5 +9,4 @@ void GitPlugin::registerTypes(const char *uri)
{ {
//@uri Git //@uri Git
qmlRegisterSingletonType<Git>(uri, 1, 0, "Git", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Git; }); qmlRegisterSingletonType<Git>(uri, 1, 0, "Git", [](QQmlEngine *, QJSEngine *) -> QObject * { return new Git; });
} }

View File

@ -1,6 +1,8 @@
#ifndef UTILS_H #ifndef UTILS_H
#define UTILS_H #define UTILS_H
#define UNUSED(x) (void)(x)
/** /**
* @brief A utility structure for enabling function overloading with template-based classes. * @brief A utility structure for enabling function overloading with template-based classes.
* see : https://stackoverflow.com/a/64018031 * see : https://stackoverflow.com/a/64018031

View File

@ -7,6 +7,7 @@ set(
pass.cpp pass.cpp
passkeyringmodel.h passkeyringmodel.h
passphraseprovider.h passphraseprovider.h
error.h
jobs/decryptjob.cpp jobs/decryptjob.cpp
jobs/deletekeyjob.cpp jobs/deletekeyjob.cpp
jobs/getkeysjob.cpp jobs/getkeysjob.cpp

71
plugins/Pass/error.h Normal file
View File

@ -0,0 +1,71 @@
// error.h
#ifndef ERROR_H
#define ERROR_H
extern "C" {
#include "rnp/rnp_err.h"
}
enum class ErrorCodeShow {
Success= 0,
UnexceptedError,
BadPassphrase,
NoKeyFound,
DecryptFailed
};
int 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);
case RNP_ERROR_KEY_NOT_FOUND:
case RNP_ERROR_NO_SUITABLE_KEY:
return static_cast<int>(ErrorCodeShow::NoKeyFound);
case RNP_ERROR_DECRYPT_FAILED:
return static_cast<int>(ErrorCodeShow::DecryptFailed);
default:
return static_cast<int>(ErrorCodeShow::UnexceptedError);
}
}
enum class ErrorCodeImportKeyFile {
Success= 0,
UnexceptedError,
BadFormat,
};
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
{
Success= 0,
Error,
};
#endif // ERROR_H

View File

@ -35,7 +35,7 @@ void DecryptJob::run()
ret = rnp_output_memory_get_buf(output, &buf, &buf_len, false); ret = rnp_output_memory_get_buf(output, &buf, &buf_len, false);
} }
if (ret == RNP_SUCCESS) { if (ret == RNP_SUCCESS) {
data = QString::fromUtf8((char*)buf); data = QString::fromUtf8((char*)buf, buf_len);
} }
rnp_input_destroy(input); rnp_input_destroy(input);

View File

@ -42,29 +42,6 @@ void ImportKeyJob::run()
// Save resulting keyring // Save resulting keyring
this->saveFullKeyring(); this->saveFullKeyring();
// rnp_output_t output = NULL;
// qDebug() << "[ImportKeyJob] Writing pubring to " << this->pubringPath();
// ret = rnp_output_to_file(&output, this->pubringPath().toLocal8Bit().constData(), RNP_OUTPUT_FILE_OVERWRITE);
// if (ret == RNP_SUCCESS) {
// qDebug() << "[ImportKeyJob] Saving key pubring ";
// ret = rnp_save_keys(this->m_ffi, RNP_KEYSTORE_GPG, output, RNP_LOAD_SAVE_PUBLIC_KEYS);
// }
// if (ret == RNP_SUCCESS) {
// ret = rnp_output_finish(output);
// }
// rnp_output_destroy(output);
// terminateOnError(ret);
// qDebug() << "[ImportKeyJob] Writing secring to " << this->secringPath();
// ret = rnp_output_to_file(&output, this->secringPath().toLocal8Bit().constData(), RNP_OUTPUT_FILE_OVERWRITE);
// if (ret == RNP_SUCCESS) {
// qDebug() << "[ImportKeyJob] Saving key secring ";
// ret = rnp_save_keys(this->m_ffi, RNP_KEYSTORE_GPG, output, RNP_LOAD_SAVE_SECRET_KEYS);
// }
// rnp_output_destroy(output);
// terminateOnError(ret);
// Emit result // Emit result
emit resultSuccess(); emit resultSuccess();

View File

@ -1,7 +1,9 @@
#include <QUrl> #include <QUrl>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QDir> #include <QtCore/QDir>
#include <QDirIterator>
#include <QtConcurrent/QtConcurrent>
#include "error.h"
#include "jobs/decryptjob.h" #include "jobs/decryptjob.h"
#include "jobs/deletekeyjob.h" #include "jobs/deletekeyjob.h"
#include "jobs/getkeysjob.h" #include "jobs/getkeysjob.h"
@ -61,8 +63,35 @@ void Pass::initPasswordStore()
qInfo() << "[Pass] Password Store is :" << m_password_store; qInfo() << "[Pass] Password Store is :" << m_password_store;
} }
void Pass::lsJob()
{
QDirIterator it(this->m_password_store, QStringList() << "*.gpg", QDir::Files, QDirIterator::Subdirectories);
QList<QString> ret;
while (it.hasNext()) {
QFile f(it.next());
QString fname = f.fileName();
fname.remove(0, this->m_password_store.length() + 1); // remove system path
ret.append(fname);
}
qInfo() << "[Pass] ls Succeed";
emit lsSucceed(ret);
this->m_sem->release(1);
}
bool Pass::ls()
{
qInfo() << "[Pass] ls";
if (!this->m_sem->tryAcquire(1, 500)) {
qInfo() << "[Pass] A command is already running";
return false;
}
QtConcurrent::run(this, &Pass::lsJob );
return true;
}
bool Pass::show(QUrl url) bool Pass::show(QUrl url)
{ {
qInfo() << "[Pass] Show";
if (!this->m_sem->tryAcquire(1, 500)) { if (!this->m_sem->tryAcquire(1, 500)) {
qInfo() << "[Pass] A command is already running"; qInfo() << "[Pass] A command is already running";
return false; return false;
@ -80,7 +109,7 @@ bool Pass::show(QUrl url)
void Pass::slotShowError(rnp_result_t err) void Pass::slotShowError(rnp_result_t err)
{ {
qInfo() << "[Pass] Show Failed"; 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); this->m_sem->release(1);
} }
@ -95,12 +124,12 @@ void Pass::slotShowSucceed(QString encrypted_file_path, QString plain_text)
bool Pass::deletePasswordStore() bool Pass::deletePasswordStore()
{ {
qInfo() << "[Pass] Delete Password Store at" << this->password_store(); qInfo() << "[Pass] Delete Password Store at" << this->m_password_store;
if (!this->m_sem->tryAcquire(1, 500)) { if (!this->m_sem->tryAcquire(1, 500)) {
qInfo() << "[Pass] A command is already running"; qInfo() << "[Pass] A command is already running";
return false; return false;
} }
auto job = new RmJob(this->password_store()); auto job = new RmJob(this->m_password_store);
connect(job, &RmJob::resultReady, this, &Pass::slotDeletePasswordStoreResult); connect(job, &RmJob::resultReady, this, &Pass::slotDeletePasswordStoreResult);
connect(job, &RmJob::finished, job, &QObject::deleteLater); connect(job, &RmJob::finished, job, &QObject::deleteLater);
job->start(); job->start();
@ -108,12 +137,13 @@ bool Pass::deletePasswordStore()
} }
void Pass::slotDeletePasswordStoreResult(bool err) void Pass::slotDeletePasswordStoreResult(bool err)
{ {
if (err) { if (err) {
qInfo() << "[Pass] delete Password Store Failed"; qInfo() << "[Pass] Delete Password Store Failed";
emit deletePasswordStoreFailed("failed to delete password store"); emit deletePasswordStoreFailed(static_cast<int>(ErrorCode::Error), "Failed to delete password store");
} else { } else {
qInfo() << "[Pass] Delete Password Store Succeed"; qInfo() << "[Pass] Delete Password Store Succeed";
this->initPasswordStore(); // reinit an empty password-store
emit deletePasswordStoreSucceed(); emit deletePasswordStoreSucceed();
} }
this->m_sem->release(1); this->m_sem->release(1);
@ -138,7 +168,7 @@ bool Pass::deleteGPGKey(PassKeyModel* key)
void Pass::slotDeleteGPGKeyError(rnp_result_t err) void Pass::slotDeleteGPGKeyError(rnp_result_t err)
{ {
qInfo() << "[Pass] Delete GPG key Failed"; qInfo() << "[Pass] Delete GPG key Failed";
emit deleteGPGKeyFailed(rnp_result_to_string(err)); emit deleteGPGKeyFailed(rnpErrorToErrorCodeGeneric(err), rnp_result_to_string(err));
this->m_sem->release(1); this->m_sem->release(1);
} }
@ -168,7 +198,7 @@ bool Pass::importGPGKey(QUrl url)
void Pass::slotImportGPGKeyError(rnp_result_t err) void Pass::slotImportGPGKeyError(rnp_result_t err)
{ {
qInfo() << "[Pass] Import GPG Key Failed"; qInfo() << "[Pass] Import GPG Key Failed";
emit importGPGKeyFailed(rnp_result_to_string(err)); emit importGPGKeyFailed(rnpErrorToErrorCodeImportKeyFile(err), rnp_result_to_string(err));
this->m_sem->release(1); this->m_sem->release(1);
} }
@ -199,7 +229,7 @@ void Pass::slotGetAllGPGKeysError(rnp_result_t err)
{ {
qInfo() << "[Pass] Get all GPG Keys Failed"; qInfo() << "[Pass] Get all GPG Keys Failed";
this->m_keyring_model = nullptr; this->m_keyring_model = nullptr;
emit getAllGPGKeysFailed(rnp_result_to_string(err)); emit getAllGPGKeysFailed(rnpErrorToErrorCodeGeneric(err), rnp_result_to_string(err));
this->m_sem->release(1); this->m_sem->release(1);
} }

View File

@ -22,8 +22,8 @@ extern "C" {
class Pass : public QObject class Pass : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString password_store MEMBER m_password_store READ password_store WRITE set_password_store ) Q_PROPERTY(QString password_store MEMBER m_password_store WRITE set_password_store )
Q_PROPERTY(QString gpg_home MEMBER m_gpg_home READ gpg_home WRITE set_gpg_home ) Q_PROPERTY(QString gpg_home MEMBER m_gpg_home WRITE set_gpg_home )
private slots: private slots:
/** /**
@ -78,7 +78,7 @@ signals:
* @brief Emitted when a GPG key deletion fails. * @brief Emitted when a GPG key deletion fails.
* @param message The error message describing the failure. * @param message The error message describing the failure.
*/ */
void deleteGPGKeyFailed(QString message); void deleteGPGKeyFailed(int err, QString message);
/** /**
* @brief Emitted when a GPG key is successfully imported. * @brief Emitted when a GPG key is successfully imported.
@ -89,7 +89,7 @@ signals:
* @brief Emitted when a GPG key import fails. * @brief Emitted when a GPG key import fails.
* @param message The error message describing the failure. * @param message The error message describing the failure.
*/ */
void importGPGKeyFailed(QString message); void importGPGKeyFailed(int err, QString message);
/** /**
* @brief Emitted when all GPG keys are successfully retrieved. * @brief Emitted when all GPG keys are successfully retrieved.
@ -101,7 +101,7 @@ signals:
* @brief Emitted when retrieving GPG keys fails. * @brief Emitted when retrieving GPG keys fails.
* @param message The error message describing the failure. * @param message The error message describing the failure.
*/ */
void getAllGPGKeysFailed(QString message); void getAllGPGKeysFailed(int err, QString message);
// Pass-related signals // Pass-related signals
/** /**
@ -118,11 +118,14 @@ signals:
*/ */
void showSucceed(QString name, QString text); void showSucceed(QString name, QString text);
void lsSucceed(QList<QString>);
/** /**
* @brief Emitted when showing a password fails. * @brief Emitted when showing a password fails.
* @param message The error message describing the failure. * @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. * @brief Emitted hen showing a password cancelled.
@ -139,7 +142,7 @@ signals:
* @brief Emitted when deleting the password store fails. * @brief Emitted when deleting the password store fails.
* @param message The error message describing the failure. * @param message The error message describing the failure.
*/ */
void deletePasswordStoreFailed(QString message); void deletePasswordStoreFailed(int err, QString message);
private: private:
QString m_password_store; /**< The path to the password store. */ QString m_password_store; /**< The path to the password store. */
@ -160,21 +163,14 @@ private:
*/ */
void initPasswordStore(); void initPasswordStore();
void lsJob();
public: public:
/** /**
* @brief Constructs the Pass object. * @brief Constructs the Pass object.
*/ */
Pass(); Pass();
/**
* @brief Gets the path to the password store.
* @return The path to the password store.
*/
QString password_store() const
{
return this->m_password_store;
};
/** /**
* @brief Set the path to the password store. * @brief Set the path to the password store.
* @param The path to the password store. * @param The path to the password store.
@ -185,15 +181,6 @@ public:
this->m_password_store = password_store; this->m_password_store = password_store;
}; };
/**
* @brief Gets the path to the gpg home.
* @return The path to the gpg home.
*/
QString gpg_home() const
{
return this->m_gpg_home;
};
/** /**
* @brief Set the path to the gpg hom. * @brief Set the path to the gpg hom.
* @param The path to the gpg hom * @param The path to the gpg hom
@ -247,6 +234,12 @@ public:
// Password store-related methods // Password store-related methods
/**
* @brief Get the list of password.
* @return The list of password in the password store.
*/
Q_INVOKABLE bool ls();
/** /**
* @brief Launch the job to 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. * @param url The URL pointing to the password store entry.

View File

@ -52,6 +52,5 @@ void UnzipJob::run()
qDebug() << dir_import_path << " to " << this->m_dir_out; qDebug() << dir_import_path << " to " << this->m_dir_out;
auto ret = dir.rename(dir_import_path, this->m_dir_out.absolutePath()); auto ret = dir.rename(dir_import_path, this->m_dir_out.absolutePath());
tmp_dir.removeRecursively();; tmp_dir.removeRecursively();;
emit resultReady(ret); emit resultReady(!ret);
} }

View File

@ -15,7 +15,7 @@ bool Utils::unzip(QUrl zip_url, QString dir_out_path)
if (!this->m_sem->tryAcquire(1, 500)) { if (!this->m_sem->tryAcquire(1, 500)) {
return false; return false;
} }
qInfo() << "Unzip path " << zip_url << " to " << dir_out_path; qInfo() << "[Utils] Unzip path " << zip_url << " to " << dir_out_path;
auto job = new UnzipJob(zip_url, QDir(dir_out_path)); auto job = new UnzipJob(zip_url, QDir(dir_out_path));
connect(job, &UnzipJob::resultReady, this, &Utils::unzipResult); connect(job, &UnzipJob::resultReady, this, &Utils::unzipResult);
connect(job, &UnzipJob::finished, job, &QObject::deleteLater); connect(job, &UnzipJob::finished, job, &QObject::deleteLater);
@ -26,13 +26,13 @@ bool Utils::unzip(QUrl zip_url, QString dir_out_path)
void Utils::unzipResult(bool err) void Utils::unzipResult(bool err)
{ {
qDebug() << "Unzip Result"; qDebug() << "[Utils] Unzip Result";
if (err) { if (err) {
qInfo() << "Unzip Failed"; qInfo() << "[Utils] Unzip Failed";
emit unzipFailed("failed to unzip archive"); emit unzipFailed();
} else { } else {
qInfo() << "Unzip Succeed"; qInfo() << "[Utils] Unzip Succeed";
emit unzipSucceed(); emit unzipSucceed();
} }
this->m_sem->release(1); this->m_sem->release(1);
@ -42,6 +42,26 @@ void Utils::unzipResult(bool err)
QString Utils::manifestPath() QString Utils::manifestPath()
{ {
auto path = QDir(QDir::currentPath()).filePath("manifest_.json"); auto path = QDir(QDir::currentPath()).filePath("manifest_.json");
qInfo() << "Manifest path : " << path; qInfo() << "[Utils] Manifest path : " << path;
return path; return path;
} }
bool Utils::rmFile(QUrl file_url)
{
return QFile::remove(file_url.toLocalFile());
}
bool Utils::rmDir(QUrl dir_url)
{
QDir dir(dir_url.toLocalFile());
return dir.removeRecursively();
}
bool Utils::fileExists(QUrl path)
{
QString p = path.toString();
auto ret = QFileInfo::exists(p) && QFileInfo(p).isFile();
qDebug() << "[Utils]" << path << "existing file :" << ret;
return ret;
}

View File

@ -34,9 +34,8 @@ signals:
/** /**
* @brief Emitted when the unzipping operation fails. * @brief Emitted when the unzipping operation fails.
* @param message The error message describing the failure.
*/ */
void unzipFailed(QString message); void unzipFailed();
private: private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */ std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
@ -48,19 +47,14 @@ public:
Utils(); Utils();
/** /**
* @brief Unzips a ZIP file to the specified output directory. * @brief Start a job to unzips a ZIP file to the specified output directory.
*
* This method extracts the contents of a ZIP file from the specified URL and saves them to the provided
* output directory path.
* *
* @param zip_url The URL of the ZIP file to unzip. * @param zip_url The URL of the ZIP file to unzip.
* @param dir_out The output directory where the contents of the ZIP file should be extracted. * @param dir_out The output directory where the contents of the ZIP file should be extracted.
* @return `true` if the unzipping operation was successful, `false` otherwise. * @return `true` if the unzipping job is started successfullly, `false` otherwise.
*/ */
Q_INVOKABLE bool unzip(QUrl zip_url, QString dir_out); Q_INVOKABLE bool unzip(QUrl zip_url, QString dir_out);
/** /**
* @brief Retrieves the path to the manifest data. * @brief Retrieves the path to the manifest data.
* *
@ -70,6 +64,30 @@ public:
*/ */
Q_INVOKABLE QString manifestPath(); Q_INVOKABLE QString manifestPath();
/**
* @brief Removes a file located at the specified URL.
*
* @param file_url The URL of the file to remove.
* @return `true` if the file was successfully removed; `false` otherwise.
*/
Q_INVOKABLE bool rmFile(QUrl file_url);
/**
* @brief Removes a directory located at the specified URL.
*
* @param dir_url The URL of the directory to remove.
* @return `true` if the directory was successfully removed; `false` otherwise.
*/
Q_INVOKABLE bool rmDir(QUrl dir_url);
/**
* @brief Verify that file exists at the specified URL.
*
* @param path The URL of the file to verfidy.
* @return `true` if the file exist; `false` otherwise.
*/
Q_INVOKABLE bool fileExists(QUrl path);
}; };
#endif #endif

268
po/nl.po Normal file
View File

@ -0,0 +1,268 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the utpass.qrouland package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-04 17:49+0100\n"
"PO-Revision-Date: 2025-02-12 22:20+0100\n"
"Last-Translator: Heimen Stoffels <vistausss@fastmail.com>\n"
"Language-Team: \n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 3.5\n"
#: ../qml/dialogs/ErrorDialog.qml:12
msgid "Error !"
msgstr "Foutmelding!"
#: ../qml/dialogs/ErrorDialog.qml:16
msgid "Close"
msgstr "Sluiten"
#: ../qml/dialogs/PassphraseDialog.qml:15
msgid "Authentication required"
msgstr "Verificatie vereist"
#: ../qml/dialogs/PassphraseDialog.qml:16
msgid "Enter passphrase:"
msgstr "Voer de toegangszin in:"
#: ../qml/dialogs/PassphraseDialog.qml:21
msgid "passphrase"
msgstr "toegangszin"
#: ../qml/dialogs/PassphraseDialog.qml:29
#: ../qml/dialogs/SimpleValidationDialog.qml:8
msgid "Ok"
msgstr "Oké"
#: ../qml/dialogs/PassphraseDialog.qml:41
#: ../qml/dialogs/SimpleValidationDialog.qml:28
msgid "Cancel"
msgstr "Annuleren"
#: ../qml/dialogs/SuccessDialog.qml:12
msgid "Success !"
msgstr "Goedgekeurd!"
#: ../qml/dialogs/SuccessDialog.qml:16
msgid "OK"
msgstr "Oké"
#: ../qml/pages/Info.qml:62
msgid "<b>Version</b>"
msgstr "<b>Versie</b>"
#: ../qml/pages/Info.qml:83
msgid "<b>Maintainer</>"
msgstr "<b>Onderhouder</b>"
#: ../qml/pages/Info.qml:110
msgid "Suggest improvement(s) or report a bug(s)"
msgstr "Ideeën delen of bugs melden"
#: ../qml/pages/Info.qml:115
msgid "Access to the source code"
msgstr "Broncode bekijken"
#: ../qml/pages/Info.qml:123
msgid "Released under the terms of the GNU GPL v3"
msgstr "Uitgebracht onder de GNU GPLv3-licentie"
#: ../qml/pages/Info.qml:132 ../qml/pages/headers/MainHeader.qml:33
msgid "Info"
msgstr "Informatie"
#: ../qml/pages/PasswordList.qml:45
msgid "No password found"
msgstr "Er zijn geen wachtwoorden beschikbaar"
#: ../qml/pages/PasswordList.qml:58
msgid "You can import a password store by cloning or"
msgstr "Importeer wachtwoorden door te klonen of een zipbestand"
#: ../qml/pages/PasswordList.qml:65
msgid "importing a password store zip in the settings"
msgstr "te importeren vanuit de instellingen"
#: ../qml/pages/PasswordList.qml:100
msgid "Decryption failed !"
msgstr "Het ongrendelen is mislukt!"
#: ../qml/pages/PasswordList.qml:114
msgid "Back"
msgstr "Terug"
#: ../qml/pages/PasswordList.qml:121 ../qml/pages/headers/MainHeader.qml:9
#: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1
msgid "UTPass"
msgstr "UTPass"
#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75
msgid "Settings"
msgstr "Instellingen"
#: ../qml/pages/headers/MainHeader.qml:57
msgid "Search"
msgstr "Zoeken"
#: ../qml/pages/settings/DeleteRepo.qml:42
#: ../qml/pages/settings/Settings.qml:58
msgid "Delete Password Store"
msgstr "Wachtwoordopslag verwijderen"
#: ../qml/pages/settings/DeleteRepo.qml:55
msgid "You're are about to delete<br>the current Password Store.<br>Continue ?"
msgstr ""
"Je staat op het punt om de huidige<br>wachtwoordopslag te verwijderen."
"<br>Weet je het zeker?"
#: ../qml/pages/settings/DeleteRepo.qml:56
#: ../qml/pages/settings/ImportZip.qml:66
#: ../qml/pages/settings/InfoKeys.qml:174
#: ../qml/pages/settings/git/ImportGitClone.qml:56
msgid "Yes"
msgstr "Ja"
#: ../qml/pages/settings/DeleteRepo.qml:69
msgid "Password Store removal failed !"
msgstr "Het verwijderen is mislukt!"
#: ../qml/pages/settings/DeleteRepo.qml:78
msgid "Password Store deleted !"
msgstr "De opslag is verwijderd!"
#: ../qml/pages/settings/DeleteRepo.qml:90
#: ../qml/pages/settings/InfoKeys.qml:216
msgid "Info Keys"
msgstr "Informatiesleutels"
#: ../qml/pages/settings/ImportKeyFile.qml:61
msgid "Key import failed !"
msgstr "Het importeren is mislukt!"
#: ../qml/pages/settings/ImportKeyFile.qml:70
msgid "Key successfully imported !"
msgstr "De sleutels zijn geïmporteerd!"
#: ../qml/pages/settings/ImportKeyFile.qml:81
msgid "GPG Key Import"
msgstr "Gpg-sleutelimport"
#: ../qml/pages/settings/ImportZip.qml:65
msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr ""
"Door een zipbestand te importeren<br>wordt de huidige opslag gewist!<br>Weet "
"je het zeker?"
#: ../qml/pages/settings/ImportZip.qml:79
msgid "Password store import failed !"
msgstr "Het importeren is mislukt!"
#: ../qml/pages/settings/ImportZip.qml:88
#: ../qml/pages/settings/git/ImportGitClone.qml:78
msgid "Password store sucessfully imported !"
msgstr "De wachtwoorden zijn geïmporteerd!"
#: ../qml/pages/settings/ImportZip.qml:100
msgid "Zip Password Store Import"
msgstr "Zipbestandsimport"
#: ../qml/pages/settings/InfoKeys.qml:47
msgid "No key found"
msgstr "Er zijn geen sleutels aangetroffen"
#: ../qml/pages/settings/InfoKeys.qml:83
msgid "Key ID :"
msgstr "Sleutel-id:"
#: ../qml/pages/settings/InfoKeys.qml:124
msgid "User IDs : "
msgstr "Gebruikers-id's: "
#: ../qml/pages/settings/InfoKeys.qml:151
msgid "Delete this key"
msgstr "Sleutel verwijderen"
#: ../qml/pages/settings/InfoKeys.qml:173
msgid "You're are about to delete<br>%1.<br>Continue ?"
msgstr "Je staat op het punt om<br>%1 te verwijderen.<br>Weet je het zeker?"
#: ../qml/pages/settings/InfoKeys.qml:187
msgid "Key removal failed !"
msgstr "Het verwijderen is mislukt!"
#: ../qml/pages/settings/InfoKeys.qml:196
msgid "Key successfully deleted !"
msgstr "De sleutel is verwijderd!"
#: ../qml/pages/settings/InfoKeys.qml:208
msgid "An Error occured getting GPG keys !"
msgstr "Er is een fout opgetreden tijdens het ophalen van de sleutels!"
#: ../qml/pages/settings/Settings.qml:23
msgid "GPG"
msgstr "Gpg"
#: ../qml/pages/settings/Settings.qml:29
msgid "Import a GPG key file"
msgstr "Gpg-sleutelbestand importeren"
#: ../qml/pages/settings/Settings.qml:34
msgid "Show GPG keys"
msgstr "Gpg-sleutels bekijken"
#: ../qml/pages/settings/Settings.qml:42
msgid "Password Store"
msgstr "Wachtwoordopslag"
#: ../qml/pages/settings/Settings.qml:48
msgid "Import a Password Store using Git"
msgstr "Wachtwoordopslag importeren met Git"
#: ../qml/pages/settings/Settings.qml:53
msgid "Import a Password Store Zip"
msgstr "Wachtwoordopslag importeren uit zipbestand"
#: ../qml/pages/settings/Settings.qml:67
msgid "Warning: importing delete any exiting Password Store"
msgstr "Waarschuwing: de huidige opslag wordt hierdoor gewist"
#: ../qml/pages/settings/git/GitCloneHttp.qml:16
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:16
msgid "Repo Url"
msgstr "Repo-url"
#: ../qml/pages/settings/git/GitCloneHttp.qml:40
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:60
msgid "Clone"
msgstr "Klonen"
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:35
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:46
msgid "Password"
msgstr "Wachtwoord"
#: ../qml/pages/settings/git/ImportGitClone.qml:55
msgid ""
"Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr ""
"Door een git-repo te importeren<br>wordt de huidige opslag gewist!<br>Weet "
"je het zeker?"
#: ../qml/pages/settings/git/ImportGitClone.qml:69
msgid "An error occured during git clone !"
msgstr "Er is een fout opgetreden tijdens het klonen!"
#: ../qml/pages/settings/git/ImportGitClone.qml:90
msgid "Git Clone Import"
msgstr "Git-kloonimport"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: utpass.qrouland\n" "Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-03 21:35+0100\n" "POT-Creation-Date: 2025-03-14 10:08+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,11 +17,69 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n" "Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: ../qml/dialogs/ErrorDialog.qml:12 #: ../qml/components/GitCloneHttp.qml:22
#: ../qml/components/GitCloneHttpAuth.qml:22
#: ../qml/components/GitCloneSshKey.qml:41
msgid "Repo Url"
msgstr ""
#: ../qml/components/GitCloneHttp.qml:47
#: ../qml/components/GitCloneHttpAuth.qml:67
#: ../qml/components/GitCloneSshKey.qml:143
msgid "Clone"
msgstr ""
#: ../qml/components/GitCloneHttpAuth.qml:42
#: ../qml/components/GitCloneHttpAuth.qml:53
msgid "Password"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:61
msgid "SSH private key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:70
msgid "Import SSH private key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:79
msgid "Delete SSH private key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:89
msgid "SSH public key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:98
msgid "Delete SSH public key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:107
msgid "Import SSH public key"
msgstr ""
#: ../qml/components/GitCloneSshKey.qml:118
#: ../qml/components/GitCloneSshKey.qml:129
msgid "Passphrase"
msgstr ""
#: ../qml/components/ImportFile.qml:18
msgid "Import succeeded !"
msgstr ""
#: ../qml/components/ImportFile.qml:19
msgid "Import failed !"
msgstr ""
#: ../qml/components/ImportFile.qml:21
msgid "File Imported"
msgstr ""
#: ../qml/dialogs/ErrorDialog.qml:13
msgid "Error !" msgid "Error !"
msgstr "" msgstr ""
#: ../qml/dialogs/ErrorDialog.qml:16 #: ../qml/dialogs/ErrorDialog.qml:17
msgid "Close" msgid "Close"
msgstr "" msgstr ""
@ -75,99 +133,138 @@ msgstr ""
msgid "Released under the terms of the GNU GPL v3" msgid "Released under the terms of the GNU GPL v3"
msgstr "" msgstr ""
#: ../qml/pages/Info.qml:132 ../qml/pages/headers/MainHeader.qml:33 #: ../qml/pages/Info.qml:132 ../qml/pages/headers/MainHeader.qml:38
msgid "Info" msgid "Info"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:45 #: ../qml/pages/PasswordList.qml:58
msgid "Bad passphrase"
msgstr ""
#: ../qml/pages/PasswordList.qml:61
msgid "No valid key found"
msgstr ""
#: ../qml/pages/PasswordList.qml:64
msgid "Decryption failed"
msgstr ""
#: ../qml/pages/PasswordList.qml:98
msgid "No password found" msgid "No password found"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:58 #: ../qml/pages/PasswordList.qml:111
msgid "You can import a password store by cloning or" msgid "You can import a password store by cloning or"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:65 #: ../qml/pages/PasswordList.qml:118
msgid "importing a password store zip in the settings" msgid "importing a password store zip in the settings"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:100 #: ../qml/pages/PasswordList.qml:195
msgid "Decryption failed !" msgid "Decryption failed !"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:114 #: ../qml/pages/PasswordList.qml:219
msgid "Back" msgid "Back"
msgstr "" msgstr ""
#: ../qml/pages/PasswordList.qml:121 ../qml/pages/headers/MainHeader.qml:9 #: ../qml/pages/PasswordList.qml:226 ../qml/pages/headers/MainHeader.qml:14
#: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1 #: ../qml/pages/headers/StackHeader.qml:9 UTPass.desktop.in.h:1
msgid "UTPass" msgid "UTPass"
msgstr "" msgstr ""
#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75 #: ../qml/pages/headers/MainHeader.qml:20
msgid "Settings" #: ../qml/pages/headers/MainHeader.qml:62
msgstr ""
#: ../qml/pages/headers/MainHeader.qml:57
msgid "Search" msgid "Search"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:42 #: ../qml/pages/headers/MainHeader.qml:31 ../qml/pages/settings/Settings.qml:73
#: ../qml/pages/settings/Settings.qml:58 msgid "Settings"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:43
#: ../qml/pages/settings/DeleteRepo.qml:93
#: ../qml/pages/settings/Settings.qml:56
msgid "Delete Password Store" msgid "Delete Password Store"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:55 #: ../qml/pages/settings/DeleteRepo.qml:56
msgid "You're are about to delete<br>the current Password Store.<br>Continue ?" msgid "You're are about to delete<br>the current Password Store.<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:56 #: ../qml/pages/settings/DeleteRepo.qml:57
#: ../qml/pages/settings/ImportZip.qml:64 #: ../qml/pages/settings/ImportGitClone.qml:142
#: ../qml/pages/settings/InfoKeys.qml:170 #: ../qml/pages/settings/ImportZip.qml:66
#: ../qml/pages/settings/git/ImportGitClone.qml:56 #: ../qml/pages/settings/InfoKeys.qml:174
msgid "Yes" msgid "Yes"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:69 #: ../qml/pages/settings/DeleteRepo.qml:70
msgid "Password Store removal failed !" msgid "Password Store removal failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:78 #: ../qml/pages/settings/DeleteRepo.qml:79
msgid "Password Store deleted !" msgid "Password Store deleted !"
msgstr "" msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:90 #: ../qml/pages/settings/ImportGitClone.qml:141
#: ../qml/pages/settings/InfoKeys.qml:212 msgid ""
msgid "Info Keys" "Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:59 #: ../qml/pages/settings/ImportGitClone.qml:155
msgid "Key import failed !" msgid "An error occured during git clone !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:68 #: ../qml/pages/settings/ImportGitClone.qml:164
msgid "Key successfully imported !" #: ../qml/pages/settings/ImportZip.qml:88
msgid "Password store sucessfully imported !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:79 #: ../qml/pages/settings/ImportGitClone.qml:176
msgid "Git Clone Import"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:7
msgid "GPG Key Import" msgid "GPG Key Import"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:63 #: ../qml/pages/settings/ImportKeyFile.qml:8
msgid "Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:9
msgid "Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:33
msgid "The file is not in a valid key format"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:10
msgid "SSH Key Import"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:11
msgid "SSH Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:12
msgid "SSH Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:65
msgid "" msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?" "Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:77 #: ../qml/pages/settings/ImportZip.qml:79
msgid "Password store import failed !" msgid "Password store import failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/ImportZip.qml:86 #: ../qml/pages/settings/ImportZip.qml:100
#: ../qml/pages/settings/git/ImportGitClone.qml:78
msgid "Password store sucessfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:98
msgid "Zip Password Store Import" msgid "Zip Password Store Import"
msgstr "" msgstr ""
@ -179,83 +276,58 @@ msgstr ""
msgid "Key ID :" msgid "Key ID :"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:120 #: ../qml/pages/settings/InfoKeys.qml:124
msgid "Users IDs : " msgid "User IDs : "
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:147 #: ../qml/pages/settings/InfoKeys.qml:151
msgid "Delete this key" msgid "Delete this key"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:169 #: ../qml/pages/settings/InfoKeys.qml:173
msgid "You're are about to delete<br>%1.<br>Continue ?" msgid "You're are about to delete<br>%1.<br>Continue ?"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:183 #: ../qml/pages/settings/InfoKeys.qml:187
msgid "Key removal failed !" msgid "Key removal failed !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:192 #: ../qml/pages/settings/InfoKeys.qml:196
msgid "Key successfully deleted !" msgid "Key successfully deleted !"
msgstr "" msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:204 #: ../qml/pages/settings/InfoKeys.qml:208
msgid "An Error occured getting GPG keys !" msgid "An Error occured getting GPG keys !"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:23 #: ../qml/pages/settings/InfoKeys.qml:216
msgid "Info Keys"
msgstr ""
#: ../qml/pages/settings/Settings.qml:21
msgid "GPG" msgid "GPG"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:29 #: ../qml/pages/settings/Settings.qml:27
msgid "Import a GPG key file" msgid "Import a GPG key file"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:34 #: ../qml/pages/settings/Settings.qml:32
msgid "Show GPG keys" msgid "Show GPG keys"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:42 #: ../qml/pages/settings/Settings.qml:40
msgid "Password Store" msgid "Password Store"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:48 #: ../qml/pages/settings/Settings.qml:46
msgid "Import a Password Store using Git" msgid "Import a Password Store using Git"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:53 #: ../qml/pages/settings/Settings.qml:51
msgid "Import a Password Store Zip" msgid "Import a Password Store Zip"
msgstr "" msgstr ""
#: ../qml/pages/settings/Settings.qml:67 #: ../qml/pages/settings/Settings.qml:65
msgid "Warning: importing delete any exiting Password Store" msgid "Warning: importing delete any exiting Password Store"
msgstr "" msgstr ""
#: ../qml/pages/settings/git/GitCloneHttp.qml:16
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:16
msgid "Repo Url"
msgstr ""
#: ../qml/pages/settings/git/GitCloneHttp.qml:40
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:60
msgid "Clone"
msgstr ""
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:35
#: ../qml/pages/settings/git/GitCloneHttpAuth.qml:46
msgid "Password"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:55
msgid ""
"Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:69
msgid "An error occured during git clone !"
msgstr ""
#: ../qml/pages/settings/git/ImportGitClone.qml:90
msgid "Git Clone Import"
msgstr ""

View File

@ -19,12 +19,14 @@ Item {
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: theme.palette.normal.background
Text { Text {
text: copyText.text text: copyText.text
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: theme.palette.normal.backgroundText
} }
Icon { Icon {

View File

@ -6,15 +6,33 @@ import Lomiri.Components.Themes 1.3
import Pass 1.0 import Pass 1.0
import QtQuick 2.4 import QtQuick 2.4
Component { Item {
//property string folder
id: fileDir
property string fName
property bool fIsDir
property bool commonBorder: true
property int lBorderwidth: 0
property int rBorderwidth: 0
property int tBorderwidth: 0
property int bBorderwidth: 0
property int commonBorderWidth: 0
property string borderColor: LomiriColors.warmGrey
signal clicked()
anchors.right: parent.right
anchors.left: parent.left
height: units.gu(5)
Rectangle { Rectangle {
anchors.right: parent.right anchors.fill: parent
anchors.left: parent.left
height: units.gu(5)
color: theme.palette.normal.background color: theme.palette.normal.background
Text { Text {
text: fileBaseName text: fileDir.fIsDir ? fileDir.fName : fileDir.fName.slice(0, -4) // remove .gpg if it's a file
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@ -26,32 +44,36 @@ Component {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: units.gu(2) anchors.rightMargin: units.gu(2)
height: units.gu(4) height: units.gu(4)
name: fileIsDir ? "go-next" : "lock" name: fileDir.fIsDir ? "go-next" : "lock"
color: LomiriColors.orange color: LomiriColors.orange
} }
MouseArea { MouseArea {
// onClicked: {
// var path = fileDir.fdfolder + "/" + fileName;
// if (fileIsDir) {
// fileDir.fdfolder = path;
// //backAction.visible = true;
// // passwordListHeader.title = fileName;
// } else {
// console.debug("pass show %1".arg(path));
// Pass.show(path);
// }
// }
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: fileDir.clicked()
var path = folderModel.folder + "/" + fileName;
if (fileIsDir) {
folderModel.folder = path;
backAction.visible = true;
passwordListHeader.title = fileName;
} else {
console.debug("pass show %1".arg(path));
Pass.show(path);
}
}
} }
CustomBorder { CustomBorder {
commonBorder: false id: cb
lBorderwidth: 0
rBorderwidth: 0 commonBorder: fileDir.commonBorder
tBorderwidth: 0 lBorderwidth: fileDir.lBorderwidth
bBorderwidth: 1 rBorderwidth: fileDir.rBorderwidth
borderColor: LomiriColors.warmGrey tBorderwidth: fileDir.tBorderwidth
bBorderwidth: fileDir.bBorderwidth
borderColor: fileDir.borderColor
} }
} }

View File

@ -4,6 +4,12 @@ import Pass 1.0
import QtQuick 2.4 import QtQuick 2.4
Column { Column {
signal repoUrlChanged(string url)
function setRepoUrl(url) {
repoUrlInput.text = url;
}
width: parent.width width: parent.width
spacing: units.gu(1) spacing: units.gu(1)
@ -24,6 +30,7 @@ Column {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width width: parent.width
placeholderText: "http(s)://<hostname>" placeholderText: "http(s)://<hostname>"
onTextChanged: repoUrlChanged(repoUrlInput.text)
} }
Rectangle { Rectangle {

View File

@ -4,6 +4,12 @@ import Pass 1.0
import QtQuick 2.4 import QtQuick 2.4
Column { Column {
signal repoUrlChanged(string url)
function setRepoUrl(url) {
repoUrlInput.text = url;
}
anchors.top: parent.fill anchors.top: parent.fill
spacing: units.gu(1) spacing: units.gu(1)
@ -24,6 +30,7 @@ Column {
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
width: parent.width width: parent.width
placeholderText: "http(s)://<username>@<hostname>" placeholderText: "http(s)://<username>@<hostname>"
onTextChanged: repoUrlChanged(repoUrlInput.text)
} }
Text { Text {

View File

@ -0,0 +1,149 @@
import Git 1.0
import Lomiri.Components 1.3
import Pass 1.0
import Utils 1.0
import QtQuick 2.4
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
signal repoUrlChanged(string url)
function setRepoUrl(url) {
repoUrlInput.text = url;
}
function update() {
__sshPrivKeyAvailable = Utils.fileExists(Git.privKey);
__sshPubKeyAvailable = Utils.fileExists(Git.pubKey);
}
Component.onCompleted: {
update();
}
anchors.top: parent.fill
spacing: units.gu(1)
Text {
id: repoUrlLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('Repo Url')
color: theme.palette.normal.backgroundText
}
TextField {
id: repoUrlInput
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
placeholderText: "<username>@<hostname>"
onTextChanged: repoUrlChanged(repoUrlInput.text)
}
Text {
id: repoPrivKeyLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('SSH private key')
color: theme.palette.normal.backgroundText
}
Button {
id: repoImportPrivKeyButton
width: parent.width
color: theme.palette.normal.positive
text: i18n.tr('Import SSH private key')
visible: !__sshPrivKeyAvailable
}
Button {
id: repoDeletePrivKeyButton
width: parent.width
color: theme.palette.normal.negative
text: i18n.tr('Delete SSH private key')
visible: __sshPrivKeyAvailable
}
Text {
id: repoPubKeyLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('SSH public key')
color: theme.palette.normal.backgroundText
}
Button {
id: repoDeletePubKeyButton
width: parent.width
color: theme.palette.normal.negative
text: i18n.tr('Delete SSH public key')
visible: __sshPrivKeyAvailable
}
Button {
id: repoImportPubKeyButton
width: parent.width
color: theme.palette.normal.positive
text: i18n.tr('Import SSH public key')
visible: !__sshPrivKeyAvailable
}
Text {
id: repoPassphraseLabel
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
text: i18n.tr('Passphrase')
color: theme.palette.normal.backgroundText
}
TextField {
id: repoPassphraseInput
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
width: parent.width
echoMode: TextInput.Password
placeholderText: i18n.tr('Passphrase')
}
Rectangle {
width: parent.width
height: units.gu(1)
color: theme.palette.normal.background
}
Button {
id: buttonClone
width: parent.width
color: theme.palette.normal.positive
text: i18n.tr('Clone')
onClicked: {
Git.cloneSshKey(repoUrlInput.text, Pass.password_store, repoPassphraseInput.text);
}
}
}

View File

@ -0,0 +1,73 @@
import "../dialogs"
import "../pages/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
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")
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
onCancelPressed: {
pageStack.pop();
}
}
ContentTransferHint {
id: transferHint
anchors.fill: parent
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
title: importKeyFilePage.headerTitle
}
}

View File

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

View File

@ -26,6 +26,7 @@ Page {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
color: theme.palette.normal.background
Flow { Flow {
id: container id: container

View File

@ -10,30 +10,83 @@ import "headers"
Page { Page {
id: passwordListPage id: passwordListPage
property string passwordStorePath property string __passwordStorePath
property var __passwords
property string __text_error_description: null
function __searchPasswords(filter) {
var ret = [];
if (__passwords) {
for (var i = 0; i < __passwords.length; i++) {
if (__passwords[i].toUpperCase().indexOf(filter.toUpperCase()) > -1)
ret.push(__passwords[i]);
}
}
return ret;
}
function __searchUpdateModel() {
const filter = passwordListHeader.searchBar.text;
console.info("filter : %1".arg(filter));
var ret = __searchPasswords(filter);
passwordListSearch.model.clear();
for (var i = 0; i < ret.length; i++) {
if (ret[i])
passwordListSearch.model.append({
"fileName": ret[i]
});
}
}
anchors.fill: parent anchors.fill: parent
Component.onCompleted: { Component.onCompleted: {
passwordStorePath = "file:" + Pass.password_store; passwordListPage.__passwordStorePath = "file:" + Pass.password_store;
Pass.onShowSucceed.connect(function(filename, text) { Pass.onShowSucceed.connect(function(filename, text) {
pageStack.push(Qt.resolvedUrl("../pages/Password.qml"), { pageStack.push(Qt.resolvedUrl("../pages/Password.qml"), {
"plainText": text, "plainText": text,
"title": filename "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); PopupUtils.open(passwordPageDecryptError);
}); });
Pass.onLsSucceed.connect(function(passwords) {
passwordListPage.__passwords = passwords;
__searchUpdateModel();
});
Pass.ls();
} }
Column { Column {
id: passwordListEmpty
anchors.top: passwordListHeader.bottom anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: units.gu(2) anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2) anchors.rightMargin: units.gu(2)
visible: folderModel.count == 0 visible: passwordListNav.model.count === 0
Rectangle { Rectangle {
width: parent.width width: parent.width
@ -71,24 +124,66 @@ Page {
} }
ListView { ListView {
id: passwordListNav
anchors.top: passwordListHeader.bottom anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
spacing: 1 spacing: 1
visible: folderModel.count != 0 visible: passwordListNav.model.count !== 0 && !passwordListHeader.searchBar.visible
model: FolderListModel { model: FolderListModel {
id: folderModel
nameFilters: ["*.gpg"] nameFilters: ["*.gpg"]
rootFolder: passwordStorePath rootFolder: passwordListPage.__passwordStorePath
folder: passwordStorePath folder: passwordListPage.__passwordStorePath
showDirs: true showDirs: true
} }
delegate: FileDir { delegate: Component {
id: fileDelegate FileDir {
fName: fileName
fIsDir: fileIsDir
onClicked: {
var path = passwordListNav.model.folder + "/" + fileName;
if (fileIsDir) {
passwordListNav.model.folder = path;
backAction.visible = true;
passwordListHeader.title = fileName;
} else {
console.debug("pass show %1".arg(path));
Pass.show(path);
}
}
}
}
}
ListView {
id: passwordListSearch
anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
visible: passwordListNav.model.count !== 0 && passwordListHeader.searchBar.visible
model: ListModel {
}
delegate: Component {
FileDir {
fName: fileName
fIsDir: false
onClicked: {
var path = passwordListPage.__passwordStorePath + "/" + fileName;
console.debug("pass show %1".arg(path));
Pass.show(path);
}
}
} }
} }
@ -98,13 +193,23 @@ Page {
ErrorDialog { ErrorDialog {
textError: i18n.tr("Decryption failed !") textError: i18n.tr("Decryption failed !")
textErrorDescription: __text_error_description
} }
}
Timer {
id: searchTimer
interval: 500
onTriggered: __searchUpdateModel()
} }
header: MainHeader { header: MainHeader {
id: passwordListHeader id: passwordListHeader
searchBar.onTextChanged: searchTimer.restart()
leadingActionBar.height: units.gu(4) leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [ leadingActionBar.actions: [
Action { Action {
@ -114,9 +219,9 @@ Page {
text: i18n.tr("Back") text: i18n.tr("Back")
visible: false visible: false
onTriggered: { onTriggered: {
folderModel.folder = folderModel.parentFolder; passwordListNav.model.folder = passwordListNav.model.parentFolder;
console.debug(folderModel.folder); console.debug(passwordListNav.model.folder);
if (folderModel.rootFolder === folderModel.folder) { if (passwordListNav.model.rootFolder === passwordListNav.model.folder) {
backAction.visible = false; backAction.visible = false;
passwordListHeader.title = i18n.tr("UTPass"); passwordListHeader.title = i18n.tr("UTPass");
} else { } else {

View File

@ -2,25 +2,30 @@ import Lomiri.Components 1.3
import QtQuick 2.4 import QtQuick 2.4
PageHeader { PageHeader {
//property alias searchBarText: searchBar.text
//signal searchBarTextChanged(string text)
id: mainHeader id: mainHeader
property alias searchBar: searchBar
width: parent.width width: parent.width
height: units.gu(6) height: units.gu(6)
title: i18n.tr("UTPass") title: i18n.tr("UTPass")
trailingActionBar.height: units.gu(4) trailingActionBar.height: units.gu(4)
trailingActionBar.numberOfSlots: 2 trailingActionBar.numberOfSlots: 2
trailingActionBar.actions: [ trailingActionBar.actions: [
/*Action { TODO Action {
iconName: "search" iconName: !searchBar.visible ? "search" : "close"
text: i18n.tr("Search") text: i18n.tr("Search")
onTriggered: { onTriggered: {
searchBar.visible = !searchBar.visible searchBar.visible = !searchBar.visible;
labelTitle.visible = !searchBar.visible labelTitle.visible = !searchBar.visible;
if (searchBar.visible === true) { if (searchBar.visible === true)
searchBar.focus = true searchBar.focus = true;
}
} }
},*/ },
Action { Action {
iconName: "settings" iconName: "settings"
text: i18n.tr("Settings") text: i18n.tr("Settings")
@ -58,8 +63,6 @@ PageHeader {
height: units.gu(4) height: units.gu(4)
visible: false visible: false
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
onFocusChanged: {
}
} }
} }

View File

@ -1,5 +1,6 @@
import "../../components" import "../../components"
import "../../dialogs" import "../../dialogs"
import "../../settings"
import "../headers" import "../headers"
import Lomiri.Components 1.3 import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3 import Lomiri.Components.Popups 1.3
@ -77,8 +78,10 @@ Page {
SuccessDialog { SuccessDialog {
textSuccess: i18n.tr("Password Store deleted !") textSuccess: i18n.tr("Password Store deleted !")
onDialogClosed: { onDialogClosed: {
pageStack.pop(); GitSettings.type = 0;
pageStack.pop(); GitSettings.repoUrl = null;
pageStack.clear();
pageStack.push(Qt.resolvedUrl("../PasswordList.qml"));
} }
} }
@ -87,7 +90,7 @@ Page {
header: StackHeader { header: StackHeader {
id: deleteRepoPageHeader id: deleteRepoPageHeader
title: i18n.tr('Info Keys') title: i18n.tr('Delete Password Store')
} }
} }

View File

@ -0,0 +1,179 @@
import "../../components"
import "../../dialogs"
import "../../settings"
import "../headers"
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Pickers 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
Page {
id: importGitClonePage
property int __gitModeHTTP : 0
property int __gitModeHTTP_AUTH : 1
property int __gitModeSSH_KEY : 2
property string __repoUrl
function __loadForm() {
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;
}
}
Component.onCompleted: {
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
Utils.rmFile(Git.privKey);
Utils.rmFile(Git.pubKey);
}
PopupUtils.open(dialogGitCloneSuccess);
});
Git.cloneFailed.connect(function() {
PopupUtils.open(dialogGitCloneError);
});
if (GitSettings.repoUrl)
__repoUrl = GitSettings.repoUrl;
if (GitSettings.type < combo.count && GitSettings.type > 0)
combo.selectedIndex = GitSettings.type;
else
combo.selectedIndex = 0;
__loadForm();
PopupUtils.open(importGitCloneValidation, importGitClonePage);
}
Column {
anchors.top: importGitCloneHeader.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
spacing: units.gu(1)
Rectangle {
width: parent.width
height: units.gu(1)
color: theme.palette.normal.background
}
OptionSelector {
id: combo
width: parent.width
model: ["HTTP", "HTTP AUTH", "SSH KEY"]
onDelegateClicked: function(i) {
timer.setTimeout(function() {
__loadForm();
}, 500);
}
Timer {
id: timer
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.triggered.connect(function release() {
timer.triggered.disconnect(cb); // This is important
timer.triggered.disconnect(release); // This is important as well
});
timer.start();
}
}
}
Loader {
id: importGitCloneForm
width: parent.width
onLoaded: {
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;
}
}
}
}
Component {
id: importGitCloneValidation
SimpleValidationDialog {
text: i18n.tr("Importing a git repo will delete<br>any existing password store!<br>Continue ?")
continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onCanceled: {
pageStack.pop();
}
}
}
Component {
id: dialogGitCloneError
ErrorDialog {
textError: i18n.tr("An error occured during git clone !")
}
}
Component {
id: dialogGitCloneSuccess
SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: {
pageStack.clear();
pageStack.push(Qt.resolvedUrl("../PasswordList.qml"));
}
}
}
header: StackHeader {
id: importGitCloneHeader
title: i18n.tr('Git Clone Import')
}
}

View File

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

@ -0,0 +1,34 @@
import "../../components"
import Utils 1.0
import Git 1.0
ImportFile {
id: importSSHKeyPage
property bool isPrivateKey
headerTitle: i18n.tr("SSH Key Import")
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);
}
}
});
}
}
}

View File

@ -32,12 +32,14 @@ Page {
if (importZipPage.activeTransfer.state === ContentTransfer.Charged) { if (importZipPage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged"); console.log("Charged");
console.log(importZipPage.activeTransfer.items[0].url); console.log(importZipPage.activeTransfer.items[0].url);
var status = Utils.unzip(importZipPage.activeTransfer.items[0].url, Pass.getPasswordStore()); var status = Utils.unzip(importZipPage.activeTransfer.items[0].url, Pass.password_store);
Utils.unzipSucceed.connect(function() { Utils.unzipSucceed.connect(function() {
Utils.rmFile(importZipPage.activeTransfer.items[0].url);
importZipPage.activeTransfer = null; importZipPage.activeTransfer = null;
PopupUtils.open(dialogImportZipPageSuccess); PopupUtils.open(dialogImportZipPageSuccess);
}); });
Utils.unzipFailed.connect(function(message) { Utils.unzipFailed.connect(function() {
Utils.rmFile(importZipPage.activeTransfer.items[0].url);
importZipPage.activeTransfer = null; importZipPage.activeTransfer = null;
PopupUtils.open(dialogImportZipPageError); PopupUtils.open(dialogImportZipPageError);
}); });
@ -85,8 +87,8 @@ Page {
SuccessDialog { SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !") textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: { onDialogClosed: {
pageStack.pop(); pageStack.clear();
pageStack.pop(); pageStack.push(Qt.resolvedUrl("../PasswordList.qml"));
} }
} }

View File

@ -89,11 +89,10 @@ Page {
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: { text: {
if (!model.modelData) { if (!model.modelData)
""; "";
} else { else
model.modelData.keyid; model.modelData.keyid;
}
} }
color: theme.palette.normal.backgroundText color: theme.palette.normal.backgroundText
} }
@ -122,7 +121,7 @@ Page {
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: i18n.tr('Users IDs : ') text: i18n.tr('User IDs : ')
color: theme.palette.normal.backgroundText color: theme.palette.normal.backgroundText
} }

View File

@ -7,8 +7,6 @@ import QtQuick 2.4
Page { Page {
id: settingsPage id: settingsPage
property string gpgKeyId: ""
Flow { Flow {
anchors.top: settingsHeader.bottom anchors.top: settingsHeader.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
@ -44,7 +42,7 @@ Page {
} }
PageStackLink { PageStackLink {
page: Qt.resolvedUrl("git/ImportGitClone.qml") page: Qt.resolvedUrl("ImportGitClone.qml")
text: i18n.tr('Import a Password Store using Git') text: i18n.tr('Import a Password Store using Git')
} }

View File

@ -1,38 +0,0 @@
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Pickers 1.3
import QtQuick 2.4
OptionSelector {
id: combo
width: parent.width
model: ["HTTP", "HTTP AUTH"]
onDelegateClicked: function(i) {
if (i === 0)
timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml");
}, 500);
else if (i === 1)
timer.setTimeout(function() {
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttpAuth.qml");
}, 500);
}
Timer {
id: timer
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.triggered.connect(function release() {
timer.triggered.disconnect(cb); // This is important
timer.triggered.disconnect(release); // This is important as well
});
timer.start();
}
}
}

View File

@ -1,93 +0,0 @@
import "../../../components"
import "../../../dialogs"
import "../../headers"
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Pass 1.0
import QtQuick 2.4
Page {
id: importGitClonePage
Component.onCompleted: {
Git.cloneSucceed.connect(function() {
PopupUtils.open(dialogGitCloneSuccess);
});
Git.cloneFailed.connect(function() {
PopupUtils.open(dialogGitCloneError);
});
PopupUtils.open(importGitCloneValidation, importGitClonePage);
importGitCloneForm.source = Qt.resolvedUrl("GitCloneHttp.qml");
}
Column {
anchors.top: importGitCloneHeader.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: parent.bottom
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
spacing: units.gu(1)
Rectangle {
width: parent.width
height: units.gu(1)
color: theme.palette.normal.background
}
GitModeOptionSelector {
id: combo
}
Loader {
id: importGitCloneForm
width: parent.width
}
}
Component {
id: importGitCloneValidation
SimpleValidationDialog {
text: i18n.tr("Importing a git repo will delete<br>any existing password store!<br>Continue ?")
continueText: i18n.tr("Yes")
continueColor: theme.palette.normal.negative
onCanceled: {
pageStack.pop();
}
}
}
Component {
id: dialogGitCloneError
ErrorDialog {
textError: i18n.tr("An error occured during git clone !")
}
}
Component {
id: dialogGitCloneSuccess
SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
}
}
}
header: StackHeader {
id: importGitCloneHeader
title: i18n.tr('Git Clone Import')
}
}

View File

@ -0,0 +1,7 @@
import Qt.labs.settings 1.0
pragma Singleton
Settings {
property int type: 0
property string repoUrl: null
}

2
qml/settings/qmldir Normal file
View File

@ -0,0 +1,2 @@
module settings
singleton GitSettings 1.0 GitSettings.qml

View File

@ -16,5 +16,5 @@ int main(int argc, char *argv[])
QGuiApplication::setApplicationName("utpass.qrouland"); QGuiApplication::setApplicationName("utpass.qrouland");
return quick_test_main(argc, argv, @TESTS_PATH@, @TESTS_PATH@); return quick_test_main(argc, argv, "@TESTS_PATH@", "@TESTS_PATH@");
} }

View File

@ -5,7 +5,6 @@ set(
SRC SRC
plugin.cpp plugin.cpp
utils.cpp utils.cpp
passphraseprovider.h
) )
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
@ -23,30 +22,6 @@ endif()
add_library(${PLUGIN} MODULE ${SRC}) add_library(${PLUGIN} MODULE ${SRC})
set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN}) set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN})
qt5_use_modules(${PLUGIN} Qml Quick DBus) qt5_use_modules(${PLUGIN} Qml Quick DBus)
set(RNP_BUILD_DIR "${CMAKE_SOURCE_DIR}/build/${ARCH_TRIPLET}/rnp/install")
INCLUDE_DIRECTORIES(${RNP_BUILD_DIR}/include)
add_library(rnp STATIC IMPORTED)
set_property(TARGET rnp PROPERTY IMPORTED_LOCATION "${RNP_BUILD_DIR}/lib/librnp.a")
add_library(gpgerror SHARED IMPORTED)
set_property(TARGET gpgerror PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpg-error.so.0.28.0")
add_library(libassuan SHARED IMPORTED)
set_property(TARGET libassuan PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libassuan.so")
add_library(libgpgme SHARED IMPORTED)
set_property(TARGET libgpgme PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpgme.so")
add_library(libgpgmepp SHARED IMPORTED)
set_property(TARGET libgpgmepp PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libgpgmepp.so")
add_library(libqgpgme SHARED IMPORTED)
set_property(TARGET libqgpgme PROPERTY IMPORTED_LOCATION "/usr/lib/${ARCH_TRIPLET}/libqgpgme.so")
target_link_libraries(${PLUGIN} rnp gpgerror libassuan libgpgme libgpgmepp libqgpgme)
set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}") set(QT_IMPORTS_DIR "/lib/${ARCH_TRIPLET}")

View File

@ -3,9 +3,9 @@
#include <QObject> #include <QObject>
#include <gpg-error.h> #include <gpg-error.h>
extern "C" { // extern "C" {
#include <rnp/rnp.h> // #include <rnp/rnp.h>
} // }
class TesTPassphraseProvider : public QObject class TesTPassphraseProvider : public QObject
{ {

View File

@ -6,11 +6,11 @@
#include <memory> #include <memory>
#include <quazip5/JlCompress.h> #include <quazip5/JlCompress.h>
#include "passphraseprovider.h" //#include "passphraseprovider.h"
#include "utils.h" #include "utils.h"
TestsUtils::TestsUtils(): TestsUtils::TestsUtils()
m_passphrase_povider(std::unique_ptr<TesTPassphraseProvider>(new TesTPassphraseProvider())) //m_passphrase_povider(std::unique_ptr<TesTPassphraseProvider>(new TesTPassphraseProvider()))
{} {}
@ -68,9 +68,9 @@ void TestsUtils::copyFolder(QUrl sourceFolderUrl, QUrl destFolderUrl)
} }
} }
QObject *TestsUtils::getTestPassphraseProvider() // QObject *TestsUtils::getTestPassphraseProvider()
{ // {
return &TesTPassphraseProvider::instance(); // return &TesTPassphraseProvider::instance();
} // }

View File

@ -1,7 +1,7 @@
#ifndef TESTSUTILS_H #ifndef TESTSUTILS_H
#define TESTSUTILS_H #define TESTSUTILS_H
#include "passphraseprovider.h" //#include "passphraseprovider.h"
#include <QObject> #include <QObject>
#include <QUrl> #include <QUrl>
#include <QQuickWindow> #include <QQuickWindow>
@ -18,7 +18,7 @@ public:
Q_INVOKABLE QString getTempPath(); Q_INVOKABLE QString getTempPath();
Q_INVOKABLE bool fileExists(QUrl path); Q_INVOKABLE bool fileExists(QUrl path);
Q_INVOKABLE void copyFolder(QUrl sourceFolder, QUrl destFolder); Q_INVOKABLE void copyFolder(QUrl sourceFolder, QUrl destFolder);
Q_INVOKABLE QObject *getTestPassphraseProvider(); //Q_INVOKABLE QObject *getTestPassphraseProvider();
}; };

View File

@ -4,6 +4,8 @@ import QtTest 1.2
import TestsUtils 1.0 import TestsUtils 1.0
TestCase { TestCase {
//Pass.passphrase_provider = TestsUtils.getTestPassphraseProvider();
property string password_store property string password_store
property string gpg_home property string gpg_home
@ -13,7 +15,6 @@ TestCase {
Pass.gpg_home = gpg_home; Pass.gpg_home = gpg_home;
password_store = TestsUtils.getTempPath(); password_store = TestsUtils.getTempPath();
Pass.password_store = password_store; Pass.password_store = password_store;
Pass.passphrase_provider = TestsUtils.getTestPassphraseProvider();
} }
} }

View File

@ -0,0 +1,44 @@
import Pass 1.0
import QtQuick 2.9
import QtTest 1.2
import TestsUtils 1.0
PassTestCase {
//TODO some additionanl error test
function init_data() {
return [{
"spy": lsSucceed,
"add_home_gpg_data": true,
"passwords": ["test.gpg"]
}, {
"spy": lsSucceed,
"add_home_gpg_data": false,
"passwords": []
}];
}
function test_ls(data) {
if (data.add_home_gpg_data === true)
TestsUtils.copyFolder(Qt.resolvedUrl("../../assets/password-store"), Qt.resolvedUrl(password_store));
var passwords;
Pass.lsSucceed.connect(function(ret) {
passwords = ret;
});
Pass.ls();
data.spy.wait();
verify(passwords.length === data.passwords.length, "Should return %1 password(s) but return %2 password(s)".arg(data.nb_password).arg(passwords.length));
for (var i = 0; data.passwords.length; i++) {
verify(passwords[i] === data.passwords[i], "%1 name should be %2 but is %3".arg(i).arg(data.passwords[i]).arg(passwords[i]));
}
}
SignalSpy {
id: lsSucceed
target: Pass
signalName: "lsSucceed"
}
}

View File

@ -4,24 +4,24 @@ import QtTest 1.2
import TestsUtils 1.0 import TestsUtils 1.0
PassTestCase { PassTestCase {
// TODO This test need to fixed by providing custom stub password provider to the pass plugin for succeed tests
//TODO some additionanl error test //TODO some additionanl error test
function init_data() { function init_data() {
return [{ return [{
"spy": showFailed, "spy": showFailed,
"err_msg": "Bad password", "err_msg": "Bad password",
"add_home_gpg_data": true, "add_password_store_data": true,
"file": "../../assets/gpg/clear_text.txt.gpg" "file": "../../assets/gpg/clear_text.txt.gpg"
}, { }, {
"spy": showFailed, "spy": showFailed,
"err_msg": "No suitable key", "err_msg": "No suitable key",
"add_home_gpg_data": false, "add_password_store_data": false,
"file": "../../assets/gpg/clear_text.txt.gpg" "file": "../../assets/gpg/clear_text.txt.gpg"
}]; }];
} }
function test_pass_show(data) { function test_pass_show(data) {
if (data.add_home_gpg_data === true) if (data.add_password_store_data === true)
TestsUtils.copyFolder(Qt.resolvedUrl("../../assets/gpghome"), Qt.resolvedUrl(gpg_home)); TestsUtils.copyFolder(Qt.resolvedUrl("../../assets/gpghome"), Qt.resolvedUrl(gpg_home));
var fname, ctext; var fname, ctext;