1
0
mirror of https://github.com/QRouland/UTPass.git synced 2026-03-17 02:56:56 +00:00

35 Commits

Author SHA1 Message Date
0b95e8e5fd Bump version 0.0.4 2026-01-06 15:47:39 +01:00
fbfd276b00 Foramtting 2026-01-06 15:42:10 +01:00
083750f429 Fix an UI GPG key import popup not showing up 2026-01-06 15:38:58 +01:00
ba7e5b5876 Disable SSH clone feature for now is not stable enough for release 2026-01-06 14:47:14 +01:00
7b7914e717 Update README to new target scope to be read only compitable with pass 2026-01-06 09:27:32 +01:00
080906740c Some GIT clone improvements (error messgages ...) 2026-01-05 21:03:50 +01:00
1fdc08eddf Fix UI GPG keys reload on delete 2026-01-05 18:45:30 +01:00
39be29f2f2 Update RNP to v0.18.1 2026-01-05 15:12:53 +01:00
e7160eeedd Fix #6 libjson dynamic loading issue for 20.04 2026-01-05 14:30:10 +01:00
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
Heimen Stoffels
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
56 changed files with 1724 additions and 517 deletions

29
CHANGELOG.md Normal file
View File

@@ -0,0 +1,29 @@
# 0.0.4
- Fix white screen issue on 24.04
- Add search functionality
- Some minor fixes and improvements
# 0.0.3
- Port app to Focal
- Improve UI :
* Follow human interface guidelines
* 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 French by Anne17 and Reda
- Added Catalan by Joan CiberSheep
- Added Spanish by Advocatux and Reda
Thanks to all the translators !
# 0.0.1
- 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)
set_source_files_properties(qml/singletons/GitSettings.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(TESTS_RUNNER)
qt5_use_modules(${PROJECT_NAME} QuickTest)
endif()

View File

@@ -1,18 +1,14 @@
# UTPass
A Ubuntu Touch password management app aiming to be compatible with [ZX2C4s pass command line application](https://www.passwordstore.org/) the standard unix password manager.
A Ubuntu Touch password management app aiming to be read-only compatible with [ZX2C4s pass command line application](https://www.passwordstore.org/) the standard unix password manager.
## Installation
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)
## Features
The goal is to be closest possible of the features offer by [ZX2C4s pass command line application](https://www.passwordstore.org/).
See [Features wiki page](https://taiga.rdrive.ovh/project/utpass/wiki/contributing) for details.
## Export/Import
@@ -20,22 +16,24 @@ Assuming that there are already passwords in another device using [ZX2C4s pas
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 with HTTP (SSH is not supported yet), you can clone your password store directly from the app. Otherwise, follow these steps to export it as a ZIP file for importing.
Export passwords to a ZIP archive, assuming they reside in the *.password-store* folder:
```
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
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)
@@ -44,11 +42,9 @@ See [Contributing wiki page](https://github.com/QRouland/UTPass/wiki/Contributin
Some useful links related to UTPass development :
* [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
* [GnuPG](https://gnupg.org/): The GNU Privacy Guard
* [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
* [Rnp](https://github.com/rnpgp/rnp) : High performance C++ OpenPGP library used by Mozilla Thunderbird
## License

View File

@@ -4,7 +4,7 @@ kill: UTPass
scripts:
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:
@@ -41,4 +41,5 @@ install_lib:
- "libhttp_parser.so*"
- "libssh2.so*"
- "libquazip5.so*"
- "libjson-c.so*"

View File

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

View File

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

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

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

View File

@@ -1,3 +1,4 @@
#include "error.h"
#include <QUrl>
#include <QtCore/QDir>
#include <QDebug>
@@ -12,8 +13,15 @@ extern "C" {
#include "jobs/gitjob.h"
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();
}
@@ -26,16 +34,15 @@ Git::~Git()
bool Git::clone(QString url, QString path, cred_type mode)
{
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;
}
auto v = overload {
[](const HTTP & x) { return "HTTP"; },
[](const HTTPUserPass & x) { return "HTTPAuth"; },
[](const SSHPass & x) { return "SSHAuth"; },
[](const SSHKey & x) { return "SSHKey"; },
[](const HTTP & x) { UNUSED(x); return "HTTP"; },
[](const HTTPUserPass & x) { UNUSED(x); return "HTTPAuth"; },
[](const SSHKey & x) { UNUSED(x); return "SSHKey"; },
};
qDebug() << "Creating clone Job " << url << " " << path << " " << std::visit(v, mode);
qDebug() << "[Git] Creating clone Job " << url << " " << path << " " << std::visit(v, mode);
CloneJob *clone_job = new CloneJob(url, path, mode);
connect(clone_job, &CloneJob::resultReady, this, &Git::cloneResult);
connect(clone_job, &CloneJob::finished, clone_job, &QObject::deleteLater);
@@ -45,25 +52,54 @@ bool Git::clone(QString url, QString path, cred_type mode)
bool Git::cloneHttp(QString url, QString path)
{
qInfo() << "Call clone command Http " << url << " " << path;
qInfo() << "[Git] Call clone command Http " << url << " " << path;
HTTP mode = {};
return this->clone(url, path, mode);
}
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 };
return this->clone(url, path, mode);
}
void Git::cloneResult(const bool err)
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 int err_code, const QString message)
{
if (err) {
emit cloneFailed(); // TODO error message
} else {
if (err_code == code_err(GitCloneErrorCode::Successful)) {
emit cloneSucceed();
} else {
emit cloneFailed(err_code, message);
}
this->m_sem->release();
}
bool Git::importSshKey(QUrl source_path, bool is_private)
{
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

@@ -1,6 +1,7 @@
#ifndef GIT_H
#define GIT_H
#include "error.h"
#include "jobs/gitjob.h"
#include <QUrl>
#include <QObject>
@@ -16,6 +17,8 @@
class Git : public QObject
{
Q_OBJECT
Q_PROPERTY(QString privKey READ pubKeyPath)
Q_PROPERTY(QString pubKey READ privKeyPath)
private slots:
/**
@@ -25,10 +28,10 @@ private slots:
* process finishes. It emits the appropriate signal based on whether the clone operation succeeded
* or failed.
*
* @param err A boolean indicating whether an error occurred during cloning. `true` if the clone failed,
* `false` if it succeeded.
* @param err A err_code indicating whether an error occurred during cloning.
* @param err An error message.
*/
void cloneResult(const bool err);
void cloneResult(const int err_code, const QString message);
signals:
/**
@@ -41,18 +44,21 @@ signals:
/**
* @brief Signal emitted when the cloning operation fails.
*
* @param err_code The error code
* @param msg The deffautl error message from libgit
* This signal is emitted when an error occurs during the cloning operation.
*/
void cloneFailed();
void cloneFailed(int err_code, QString msg);
private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
QDir m_ssh_homedir; /**< Directory that contains the SSH keys (public and private). */
/**
* @brief Clones a repository from a specified URL.
*
* This private method initiates the cloning job. It sets up the repository cloning process based on
* the specified URL, destination path, and cloning mode (HTTP or SSH).
* the specified URL, destination path, and cloning mode (HTTP, HTTP_AUTH or SSH).
*
* @param url The URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
@@ -61,30 +67,53 @@ private:
*/
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:
/**
* @brief Constructor for the Git class.
*
* Initializes the `Git` class, setting up necessary resources such as the semaphore for concurrent operation management.
* Initializes the `Git` class.
*/
Git();
/**
* @brief Destructor for the Git class.
*
* Cleans up any resources used by the `Git` class, ensuring that no ongoing operations or resources are left hanging.
* Cleans up any resources used by the `Git` class.
*/
~Git() override;
Q_INVOKABLE bool importSshKey(QUrl source_path, bool is_private);
/**
* @brief Clones a repository over HTTP.
*
* This method clones a Git repository from the specified HTTP URL and saves it to the given destination path.
* It is a wrapper around the private `clone()` method, specifying the HTTP cloning mode.
*
* @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @return `true` if the clone operation was successful, `false` otherwise.
* @return `true` if the clone operation was successfully started, `false` otherwise.
*/
Q_INVOKABLE bool cloneHttp(QString url, QString path);
@@ -97,13 +126,22 @@ public:
* @param url The HTTP URL of the Git repository to clone.
* @param path The destination path for the cloned repository.
* @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);
// Future SSH support methods:
// Q_INVOKABLE bool clone_ssh_pass(QString url, QString path, QString pass);
// Q_INVOKABLE bool clone_ssh_key(QString url, QString path, QString pub_key, QString priv_key, QString passphrase);
/**
* @brief Clones a repository over SSH with a key for authentication.
*
* 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);
// ....

View File

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

View File

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

View File

@@ -18,36 +18,57 @@ GitJob::~GitJob()
git_libgit2_shutdown();
}
int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_from_url,
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";
p->err = ErrorCodeCB::NoUsername;
return (int) GIT_EUSER;
}
if (p->called) {
qWarning() << "[GitJob] credentials_cb : cb already called, probably invalid creds";
p->err = ErrorCodeCB::InvalidCreds;
return (int) GIT_EUSER;
}
p->called = true;
auto v = overload {
[](const HTTP & x)
{
qDebug() << "credentialsCB : HTTP ";
qWarning() << "credentialsCB : callback should never be call for HTTP ";
UNUSED(x);
qDebug() << "[GitJob] credentialsCB : HTTP ";
qWarning() << "[GitJob] credentialsCB : callback should never be call for HTTP";
return (int) GIT_EUSER;
},
[&out, &username_from_url](const HTTPUserPass & x)
[allowed_types, &out, &username_from_url, &p](const HTTPUserPass & x)
{
qDebug() << "credentialsCB : HTTPUserPass ";
if (!username_from_url) {
qWarning() << "credentials_cb : no username provided ";
qDebug() << "[GitJob] credentialsCB : HTTPUserPass ";
if (!(allowed_types & GIT_CREDTYPE_USERPASS_PLAINTEXT)) {
qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for HTTPUserPass creds";
p->err = ErrorCodeCB::UrlTypeDoNotMatchCreds;
return (int) GIT_EUSER;
}
return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData());
},
[](const SSHPass & x)
[allowed_types, &out, &username_from_url, &p](const SSHKey & x)
{
qWarning() << "credentials_cb : SSHAuth to be implemented ";
return (int) GIT_EUSER;
}, // TODO
[](const SSHKey & x)
{
qWarning() << "credentials_cb : SSHKey to be implemented ";
return (int) GIT_EUSER;
} // TODO
qDebug() << "[GitJob] credentialsCB : SSHKey ";
if (!(allowed_types & GIT_CREDTYPE_SSH_KEY)) {
qWarning() << "[GitJob] credentials_cb : allowed_types is invalid for SSHKey creds";
p->err = ErrorCodeCB::UrlTypeDoNotMatchCreds;
return (int) GIT_EUSER;
}
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
#define GITJOB_H
#include <QDir>
#include <QThread>
extern "C" {
#include <git2.h>
@@ -8,12 +9,17 @@ extern "C" {
#include <variant>
// Forward declarations for the different credential types.
struct HTTP { };
struct HTTP {};
struct HTTPUserPass {
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.
@@ -21,10 +27,24 @@ struct SSHKey { };
* This type is used to store one of the following credential types:
* - HTTP
* - HTTPUserPass
* - SSHPass
* - SSHKey
*/
typedef std::variant<HTTP, HTTPUserPass, SSHPass, SSHKey> cred_type;
typedef std::variant<HTTP, HTTPUserPass, SSHKey> cred_type;
enum class ErrorCodeCB {
None = 0,
NoUsername,
UrlTypeDoNotMatchCreds,
InvalidCreds,
};
struct PayloadCB {
bool called;
cred_type creds;
ErrorCodeCB err;
PayloadCB(bool ca, cred_type cr): called(ca), creds(cr), err(ErrorCodeCB::None) {}
};
/**
* @class GitJob

View File

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

View File

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

View File

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

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

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

View File

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

View File

@@ -42,29 +42,6 @@ void ImportKeyJob::run()
// Save resulting keyring
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 resultSuccess();

View File

@@ -1,7 +1,9 @@
#include <QUrl>
#include <QtCore/QStandardPaths>
#include <QtCore/QDir>
#include <QDirIterator>
#include <QtConcurrent/QtConcurrent>
#include "error.h"
#include "jobs/decryptjob.h"
#include "jobs/deletekeyjob.h"
#include "jobs/getkeysjob.h"
@@ -61,8 +63,35 @@ void Pass::initPasswordStore()
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)
{
qInfo() << "[Pass] Show";
if (!this->m_sem->tryAcquire(1, 500)) {
qInfo() << "[Pass] A command is already running";
return false;
@@ -80,7 +109,7 @@ bool Pass::show(QUrl url)
void Pass::slotShowError(rnp_result_t err)
{
qInfo() << "[Pass] Show Failed";
emit showFailed(rnp_result_to_string(err));
emit showFailed((int) rnpErrorToErrorCodeShow(err), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -95,12 +124,12 @@ void Pass::slotShowSucceed(QString encrypted_file_path, QString plain_text)
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)) {
qInfo() << "[Pass] A command is already running";
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::finished, job, &QObject::deleteLater);
job->start();
@@ -110,10 +139,11 @@ bool Pass::deletePasswordStore()
void Pass::slotDeletePasswordStoreResult(bool err)
{
if (err) {
qInfo() << "[Pass] delete Password Store Failed";
emit deletePasswordStoreFailed("failed to delete password store");
qInfo() << "[Pass] Delete Password Store Failed";
emit deletePasswordStoreFailed(static_cast<int>(ErrorCode::Error), "Failed to delete password store");
} else {
qInfo() << "[Pass] Delete Password Store Succeed";
this->initPasswordStore(); // reinit an empty password-store
emit deletePasswordStoreSucceed();
}
this->m_sem->release(1);
@@ -138,7 +168,7 @@ bool Pass::deleteGPGKey(PassKeyModel* key)
void Pass::slotDeleteGPGKeyError(rnp_result_t err)
{
qInfo() << "[Pass] Delete GPG key Failed";
emit deleteGPGKeyFailed(rnp_result_to_string(err));
emit deleteGPGKeyFailed(static_cast<int>(ErrorCode::Error), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -168,7 +198,7 @@ bool Pass::importGPGKey(QUrl url)
void Pass::slotImportGPGKeyError(rnp_result_t err)
{
qInfo() << "[Pass] Import GPG Key Failed";
emit importGPGKeyFailed(rnp_result_to_string(err));
emit importGPGKeyFailed((int) rnpErrorToErrorCodeImportKeyFile(err), rnp_result_to_string(err));
this->m_sem->release(1);
}
@@ -199,7 +229,7 @@ void Pass::slotGetAllGPGKeysError(rnp_result_t err)
{
qInfo() << "[Pass] Get all GPG Keys Failed";
this->m_keyring_model = nullptr;
emit getAllGPGKeysFailed(rnp_result_to_string(err));
emit getAllGPGKeysFailed(static_cast<int>(ErrorCode::Error), rnp_result_to_string(err));
this->m_sem->release(1);
}

View File

@@ -22,8 +22,8 @@ extern "C" {
class Pass : public QObject
{
Q_OBJECT
Q_PROPERTY(QString password_store MEMBER m_password_store READ 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 password_store MEMBER m_password_store WRITE set_password_store )
Q_PROPERTY(QString gpg_home MEMBER m_gpg_home WRITE set_gpg_home )
private slots:
/**
@@ -78,7 +78,7 @@ signals:
* @brief Emitted when a GPG key deletion fails.
* @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.
@@ -89,7 +89,7 @@ signals:
* @brief Emitted when a GPG key import fails.
* @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.
@@ -101,7 +101,7 @@ signals:
* @brief Emitted when retrieving GPG keys fails.
* @param message The error message describing the failure.
*/
void getAllGPGKeysFailed(QString message);
void getAllGPGKeysFailed(int err, QString message);
// Pass-related signals
/**
@@ -118,11 +118,14 @@ signals:
*/
void showSucceed(QString name, QString text);
void lsSucceed(QList<QString>);
/**
* @brief Emitted when showing a password fails.
* @param message The error message describing the failure.
*/
void showFailed(QString message);
void showFailed(int err, QString message);
/**
* @brief Emitted hen showing a password cancelled.
@@ -139,7 +142,7 @@ signals:
* @brief Emitted when deleting the password store fails.
* @param message The error message describing the failure.
*/
void deletePasswordStoreFailed(QString message);
void deletePasswordStoreFailed(int err, QString message);
private:
QString m_password_store; /**< The path to the password store. */
@@ -160,21 +163,14 @@ private:
*/
void initPasswordStore();
void lsJob();
public:
/**
* @brief Constructs the Pass object.
*/
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.
* @param The path to the password store.
@@ -185,15 +181,6 @@ public:
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.
* @param The path to the gpg hom
@@ -247,6 +234,12 @@ public:
// 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.
* @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;
auto ret = dir.rename(dir_import_path, this->m_dir_out.absolutePath());
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)) {
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));
connect(job, &UnzipJob::resultReady, this, &Utils::unzipResult);
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)
{
qDebug() << "Unzip Result";
qDebug() << "[Utils] Unzip Result";
if (err) {
qInfo() << "Unzip Failed";
emit unzipFailed("failed to unzip archive");
qInfo() << "[Utils] Unzip Failed";
emit unzipFailed();
} else {
qInfo() << "Unzip Succeed";
qInfo() << "[Utils] Unzip Succeed";
emit unzipSucceed();
}
this->m_sem->release(1);
@@ -42,6 +42,26 @@ void Utils::unzipResult(bool err)
QString Utils::manifestPath()
{
auto path = QDir(QDir::currentPath()).filePath("manifest_.json");
qInfo() << "Manifest path : " << path;
qInfo() << "[Utils] Manifest path : " << 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.
* @param message The error message describing the failure.
*/
void unzipFailed(QString message);
void unzipFailed();
private:
std::unique_ptr<QSemaphore> m_sem; /**< Semaphore for managing concurrent operations. */
@@ -48,19 +47,14 @@ public:
Utils();
/**
* @brief 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.
* @brief Start a job to unzips a ZIP file to the specified output directory.
*
* @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.
* @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);
/**
* @brief Retrieves the path to the manifest data.
*
@@ -70,6 +64,30 @@ public:
*/
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

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 ""
"Project-Id-Version: utpass.qrouland\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-03 21:35+0100\n"
"POT-Creation-Date: 2026-01-05 19:03+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,11 +17,69 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\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 !"
msgstr ""
#: ../qml/dialogs/ErrorDialog.qml:16
#: ../qml/dialogs/ErrorDialog.qml:17
msgid "Close"
msgstr ""
@@ -75,99 +133,159 @@ msgstr ""
msgid "Released under the terms of the GNU GPL v3"
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"
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"
msgstr ""
#: ../qml/pages/PasswordList.qml:58
#: ../qml/pages/PasswordList.qml:111
msgid "You can import a password store by cloning or"
msgstr ""
#: ../qml/pages/PasswordList.qml:65
#: ../qml/pages/PasswordList.qml:118
msgid "importing a password store zip in the settings"
msgstr ""
#: ../qml/pages/PasswordList.qml:100
#: ../qml/pages/PasswordList.qml:195
msgid "Decryption failed !"
msgstr ""
#: ../qml/pages/PasswordList.qml:114
#: ../qml/pages/PasswordList.qml:219
msgid "Back"
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
msgid "UTPass"
msgstr ""
#: ../qml/pages/headers/MainHeader.qml:26 ../qml/pages/settings/Settings.qml:75
msgid "Settings"
msgstr ""
#: ../qml/pages/headers/MainHeader.qml:57
#: ../qml/pages/headers/MainHeader.qml:20
#: ../qml/pages/headers/MainHeader.qml:62
msgid "Search"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:42
#: ../qml/pages/settings/Settings.qml:58
#: ../qml/pages/headers/MainHeader.qml:31 ../qml/pages/settings/Settings.qml:73
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"
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 ?"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:56
#: ../qml/pages/settings/ImportZip.qml:64
#: ../qml/pages/settings/InfoKeys.qml:170
#: ../qml/pages/settings/git/ImportGitClone.qml:56
#: ../qml/pages/settings/DeleteRepo.qml:57
#: ../qml/pages/settings/ImportGitClone.qml:169
#: ../qml/pages/settings/ImportZip.qml:66
#: ../qml/pages/settings/InfoKeys.qml:174
msgid "Yes"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:69
#: ../qml/pages/settings/DeleteRepo.qml:70
msgid "Password Store removal failed !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:78
#: ../qml/pages/settings/DeleteRepo.qml:79
msgid "Password Store deleted !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:90
#: ../qml/pages/settings/InfoKeys.qml:212
msgid "Info Keys"
#: ../qml/pages/settings/ImportGitClone.qml:56
msgid "An unexpected error occurred during the clone operation."
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:59
msgid "Key import failed !"
#: ../qml/pages/settings/ImportGitClone.qml:59
msgid "Invalid URL for the current clone operation."
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:68
msgid "Key successfully imported !"
#: ../qml/pages/settings/ImportGitClone.qml:62
msgid "Username is missing in the URL."
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:79
#: ../qml/pages/settings/ImportGitClone.qml:65
msgid "Authentication error, credentials may be invalid."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:68
msgid ""
"Credentials type does not match the URL for the current clone operation."
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:168
msgid ""
"Importing a git repo will delete<br>any existing password store!"
"<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:182
msgid "An error occured during git clone !"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:192
#: ../qml/pages/settings/ImportZip.qml:88
msgid "Password store sucessfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportGitClone.qml:204
msgid "Git Clone Import"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:9
msgid "GPG Key Import"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:63
#: ../qml/pages/settings/ImportKeyFile.qml:10
msgid "Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:11
msgid "Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:35
msgid "The file is not in a valid key format"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:12
msgid "SSH Key Import"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:13
msgid "SSH Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportSSHkey.qml:14
msgid "SSH Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:65
msgid ""
"Importing a new zip will delete<br>any existing password store!<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:77
#: ../qml/pages/settings/ImportZip.qml:79
msgid "Password store import failed !"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:86
#: ../qml/pages/settings/git/ImportGitClone.qml:78
msgid "Password store sucessfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:98
#: ../qml/pages/settings/ImportZip.qml:100
msgid "Zip Password Store Import"
msgstr ""
@@ -179,83 +297,58 @@ msgstr ""
msgid "Key ID :"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:120
msgid "Users IDs : "
#: ../qml/pages/settings/InfoKeys.qml:124
msgid "User IDs : "
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:147
#: ../qml/pages/settings/InfoKeys.qml:151
msgid "Delete this key"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:169
#: ../qml/pages/settings/InfoKeys.qml:173
msgid "You're are about to delete<br>%1.<br>Continue ?"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:183
#: ../qml/pages/settings/InfoKeys.qml:187
msgid "Key removal failed !"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:192
#: ../qml/pages/settings/InfoKeys.qml:196
msgid "Key successfully deleted !"
msgstr ""
#: ../qml/pages/settings/InfoKeys.qml:204
#: ../qml/pages/settings/InfoKeys.qml:208
msgid "An Error occured getting GPG keys !"
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"
msgstr ""
#: ../qml/pages/settings/Settings.qml:29
#: ../qml/pages/settings/Settings.qml:27
msgid "Import a GPG key file"
msgstr ""
#: ../qml/pages/settings/Settings.qml:34
#: ../qml/pages/settings/Settings.qml:32
msgid "Show GPG keys"
msgstr ""
#: ../qml/pages/settings/Settings.qml:42
#: ../qml/pages/settings/Settings.qml:40
msgid "Password Store"
msgstr ""
#: ../qml/pages/settings/Settings.qml:48
#: ../qml/pages/settings/Settings.qml:46
msgid "Import a Password Store using Git"
msgstr ""
#: ../qml/pages/settings/Settings.qml:53
#: ../qml/pages/settings/Settings.qml:51
msgid "Import a Password Store Zip"
msgstr ""
#: ../qml/pages/settings/Settings.qml:67
#: ../qml/pages/settings/Settings.qml:65
msgid "Warning: importing delete any exiting Password Store"
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 {
anchors.fill: parent
color: theme.palette.normal.background
Text {
text: copyText.text
anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter
color: theme.palette.normal.backgroundText
}
Icon {

View File

@@ -6,15 +6,33 @@ import Lomiri.Components.Themes 1.3
import Pass 1.0
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 {
anchors.right: parent.right
anchors.left: parent.left
height: units.gu(5)
anchors.fill: parent
color: theme.palette.normal.background
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.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter
@@ -26,32 +44,36 @@ Component {
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: units.gu(2)
height: units.gu(4)
name: fileIsDir ? "go-next" : "lock"
name: fileDir.fIsDir ? "go-next" : "lock"
color: LomiriColors.orange
}
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
onClicked: {
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);
}
}
onClicked: fileDir.clicked()
}
CustomBorder {
commonBorder: false
lBorderwidth: 0
rBorderwidth: 0
tBorderwidth: 0
bBorderwidth: 1
borderColor: LomiriColors.warmGrey
id: cb
commonBorder: fileDir.commonBorder
lBorderwidth: fileDir.lBorderwidth
rBorderwidth: fileDir.rBorderwidth
tBorderwidth: fileDir.tBorderwidth
bBorderwidth: fileDir.bBorderwidth
borderColor: fileDir.borderColor
}
}

View File

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

View File

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

View File

@@ -0,0 +1,145 @@
import Git 1.0
import Lomiri.Components 1.3
import Pass 1.0
import QtQuick 2.4
import Utils 1.0
Column {
property alias importSshPrivKeyButton: repoImportPrivKeyButton
property alias importSshPubKeyButton: repoImportPubKeyButton
property alias deleteSshPrivKeyButton: repoDeletePrivKeyButton
property alias deleteSshPubKeyButton: repoDeletePubKeyButton
property bool __sshPrivKeyAvailable: false
property bool __sshPubKeyAvailable: false
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,48 @@
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 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
}
header: StackHeader {
id: importKeyHeader
title: importKeyFilePage.headerTitle
}
}

View File

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

View File

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

View File

@@ -10,30 +10,87 @@ import "headers"
Page {
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
Component.onCompleted: {
passwordStorePath = "file:" + Pass.password_store;
passwordListPage.__passwordStorePath = "file:" + Pass.password_store;
Pass.onShowSucceed.connect(function(filename, text) {
pageStack.push(Qt.resolvedUrl("../pages/Password.qml"), {
"plainText": text,
"title": filename
});
});
Pass.onShowFailed.connect(function(message) {
Pass.onShowFailed.connect(function(code, message) {
switch (code) {
case 1:
// UnexceptedError -> use the default (not translate) rnp error
__text_error_description = message;
break;
case 2:
// BadPassphrase
__text_error_description = i18n.tr("Bad passphrase");
break;
case 3:
// NoKeyFound
__text_error_description = i18n.tr("No valid key found");
break;
case 3:
// DecryptFailed
__text_error_description = i18n.tr("Decryption failed");
break;
default:
console.warn("Unhandled error code");
__text_error_description = message;
break;
}
PopupUtils.open(passwordPageDecryptError);
});
Pass.onLsSucceed.connect(function(passwords) {
passwordListPage.__passwords = passwords;
__searchUpdateModel();
});
Pass.ls();
}
Column {
id: passwordListEmpty
anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.rightMargin: units.gu(2)
visible: folderModel.count == 0
visible: passwordListNav.model.count === 0
Rectangle {
width: parent.width
@@ -71,24 +128,66 @@ Page {
}
ListView {
id: passwordListNav
anchors.top: passwordListHeader.bottom
anchors.bottom: parent.bottom
anchors.right: parent.right
anchors.left: parent.left
spacing: 1
visible: folderModel.count != 0
visible: passwordListNav.model.count !== 0 && !passwordListHeader.searchBar.visible
model: FolderListModel {
id: folderModel
nameFilters: ["*.gpg"]
rootFolder: passwordStorePath
folder: passwordStorePath
rootFolder: passwordListPage.__passwordStorePath
folder: passwordListPage.__passwordStorePath
showDirs: true
}
delegate: FileDir {
id: fileDelegate
delegate: Component {
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 +197,22 @@ Page {
ErrorDialog {
textError: i18n.tr("Decryption failed !")
textErrorDescription: __text_error_description
}
}
Timer {
id: searchTimer
interval: 500
onTriggered: __searchUpdateModel()
}
header: MainHeader {
id: passwordListHeader
searchBar.onTextChanged: searchTimer.restart()
leadingActionBar.height: units.gu(4)
leadingActionBar.actions: [
Action {
@@ -114,9 +222,9 @@ Page {
text: i18n.tr("Back")
visible: false
onTriggered: {
folderModel.folder = folderModel.parentFolder;
console.debug(folderModel.folder);
if (folderModel.rootFolder === folderModel.folder) {
passwordListNav.model.folder = passwordListNav.model.parentFolder;
console.debug(passwordListNav.model.folder);
if (passwordListNav.model.rootFolder === passwordListNav.model.folder) {
backAction.visible = false;
passwordListHeader.title = i18n.tr("UTPass");
} else {

View File

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

View File

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

View File

@@ -0,0 +1,209 @@
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 {
// property int __gitModeSSH_KEY : 2
id: importGitClonePage
property int __gitModeHTTP: 0
property int __gitModeHTTP_AUTH: 1
property int __gitCloneErrorCodeSuccessful: 0
property int __gitCloneErrorCodeUnexpectedError: 1
property int __gitCloneErrorCodeInvalidUrl: 2
property int __gitCloneErrorCodeNoUsername: 3
property int __gitCloneErrorCodeAuthentificationError: 4
property int __gitCloneErrorCodeUrlTypeDoNotMatchCreds: 5
property string __repoUrl
property string __err_message: null
function __loadForm() {
// case __gitModeSSH_KEY:
// importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneSshKey.qml");
// break;
switch (combo.selectedIndex) {
case __gitModeHTTP:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttp.qml");
break;
case __gitModeHTTP_AUTH:
importGitCloneForm.source = Qt.resolvedUrl("../../components/GitCloneHttpAuth.qml");
break;
}
}
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, importGitClonePage);
});
Git.cloneFailed.connect(function(err_code, msg) {
switch (err_code) {
case __gitCloneErrorCodeUnexpectedError:
__err_message = i18n.tr("An error occurred during the clone operation.");
break;
case __gitCloneErrorCodeInvalidUrl:
__err_message = i18n.tr("Invalid URL for the current clone operation.");
break;
case __gitCloneErrorCodeNoUsername:
__err_message = i18n.tr("Username is missing in the URL.");
break;
case __gitCloneErrorCodeAuthentificationError:
__err_message = i18n.tr("Authentication error, credentials may be invalid.");
break;
case __gitCloneErrorCodeUrlTypeDoNotMatchCreds:
__err_message = i18n.tr("Credentials type does not match the URL for the current clone operation.");
break;
default:
__err_message = msg;
break;
}
PopupUtils.open(dialogGitCloneError, importGitClonePage);
});
if (GitSettings.repoUrl)
__repoUrl = GitSettings.repoUrl;
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: {
// case __gitModeSSH_KEY:
// importGitCloneForm.item.importSshPrivKeyButton.clicked.connect(function() {
// pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
// "isPrivateKey": true
// });
// });
// importGitCloneForm.item.importSshPubKeyButton.clicked.connect(function() {
// pageStack.push(Qt.resolvedUrl("ImportSSHkey.qml"), {
// "isPrivateKey": false
// });
// });
// break;
importGitCloneForm.item.repoUrlChanged.connect(function(url) {
importGitClonePage.__repoUrl = url;
});
importGitCloneForm.item.setRepoUrl(importGitClonePage.__repoUrl);
switch (combo.selectedIndex) {
case __gitModeHTTP:
break;
case __gitModeHTTP_AUTH:
break;
}
}
}
}
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 !")
textErrorDescription: __err_message
}
}
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

@@ -11,8 +11,12 @@ Page {
id: importKeyFilePage
property var activeTransfer
property alias contentPicker: contentPicker
property string __text_error_description: null
ContentPeerPicker {
id: contentPicker
anchors.top: importKeyHeader.bottom
anchors.bottom: parent.bottom
anchors.topMargin: importKeyFilePage.header.height
@@ -22,23 +26,41 @@ Page {
contentType: ContentType.Text
handler: ContentHandler.Source
onPeerSelected: {
peer.selectionType = ContentTransfer.Single;
importKeyFilePage.activeTransfer = peer.request();
importKeyFilePage.activeTransfer.stateChanged.connect(function() {
if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importKeyFilePage.activeTransfer.items[0].url);
var status = Pass.importGPGKey(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKeySucceed.connect(function() {
importKeyFilePage.activeTransfer = null;
PopupUtils.open(dialogImportKeyPageSucess);
});
Pass.importGPGKeyFailed.connect(function(message) {
importKeyFilePage.activeTransfer = null;
PopupUtils.open(dialogImportKeyPageError);
});
}
});
{
importKeyFilePage.contentPicker.peer.selectionType = ContentTransfer.Single;
importKeyFilePage.activeTransfer = importKeyFilePage.contentPicker.peer.request();
importKeyFilePage.activeTransfer.stateChanged.connect(function() {
if (importKeyFilePage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKey(importKeyFilePage.activeTransfer.items[0].url);
Pass.importGPGKeySucceed.connect(function() {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
PopupUtils.open(dialogImportKeyPageSucess);
});
Pass.importGPGKeyFailed.connect(function(err, message) {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
switch (code) {
case 1:
// UnexceptedError -> use the default (not translate) rnp error
__text_error_description = message;
break;
case 2:
// BadFormat
__text_error_description = i18n.tr("The file is not in a valid key format");
break;
default:
console.warn("Unhandled error code");
__text_error_description = message;
break;
}
PopupUtils.open(dialogImportKeyPageError);
});
}
});
};
}
onCancelPressed: {
pageStack.pop();
@@ -57,6 +79,7 @@ Page {
ErrorDialog {
textError: i18n.tr("Key import failed !")
textErrorDescription: __text_error_description
}
}

View File

@@ -0,0 +1,35 @@
import "../../components"
import Git 1.0
import Lomiri.Components 1.3
import Lomiri.Components.Popups 1.3
import Lomiri.Content 1.3
import Utils 1.0
ImportFile {
id: importSSHKeyPage
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: {
{
importSSHKeyPage.contentPicker.peer.selectionType = ContentTransfer.Single;
importSSHKeyPage.activeTransfer = importSSHKeyPage.contentPicker.peer.request();
importSSHKeyPage.activeTransfer.stateChanged.connect(function() {
if (importSSHKeyPage.activeTransfer.state === ContentTransfer.Charged) {
console.log("Charged");
console.log(importSSHKeyPage.activeTransfer.items[0].url);
var ret = Git.importSshKey(importSSHKeyPage.activeTransfer.items[0].url, isPrivateKey);
Utils.rmFile(importSSHKeyPage.activeTransfer.items[0].url);
importSSHKeyPage.activeTransfer = null;
if (ret)
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageSucess);
else
PopupUtils.open(importSSHKeyPage.dialogImportKeyPageError);
}
});
};
}
}

View File

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

View File

@@ -89,11 +89,10 @@ Page {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: {
if (!model.modelData) {
if (!model.modelData)
"";
} else {
else
model.modelData.keyid;
}
}
color: theme.palette.normal.backgroundText
}
@@ -122,7 +121,7 @@ Page {
width: parent.width
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: i18n.tr('Users IDs : ')
text: i18n.tr('User IDs : ')
color: theme.palette.normal.backgroundText
}
@@ -196,7 +195,7 @@ Page {
SuccessDialog {
textSuccess: i18n.tr("Key successfully deleted !")
onDialogClosed: {
infoKeysListView.model = Pass.getAllGPGKeys();
Pass.getAllGPGKeys();
}
}

View File

@@ -7,8 +7,6 @@ import QtQuick 2.4
Page {
id: settingsPage
property string gpgKeyId: ""
Flow {
anchors.top: settingsHeader.bottom
anchors.bottom: parent.bottom
@@ -44,7 +42,7 @@ Page {
}
PageStackLink {
page: Qt.resolvedUrl("git/ImportGitClone.qml")
page: Qt.resolvedUrl("ImportGitClone.qml")
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");
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
plugin.cpp
utils.cpp
passphraseprovider.h
)
set(CMAKE_AUTOMOC ON)
@@ -23,30 +22,6 @@ endif()
add_library(${PLUGIN} MODULE ${SRC})
set_target_properties(${PLUGIN} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PLUGIN})
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}")

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
#ifndef TESTSUTILS_H
#define TESTSUTILS_H
#include "passphraseprovider.h"
//#include "passphraseprovider.h"
#include <QObject>
#include <QUrl>
#include <QQuickWindow>
@@ -18,7 +18,7 @@ public:
Q_INVOKABLE QString getTempPath();
Q_INVOKABLE bool fileExists(QUrl path);
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
TestCase {
//Pass.passphrase_provider = TestsUtils.getTestPassphraseProvider();
property string password_store
property string gpg_home
@@ -13,7 +15,6 @@ TestCase {
Pass.gpg_home = gpg_home;
password_store = TestsUtils.getTempPath();
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
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
function init_data() {
return [{
"spy": showFailed,
"err_msg": "Bad password",
"add_home_gpg_data": true,
"add_password_store_data": true,
"file": "../../assets/gpg/clear_text.txt.gpg"
}, {
"spy": showFailed,
"err_msg": "No suitable key",
"add_home_gpg_data": false,
"add_password_store_data": false,
"file": "../../assets/gpg/clear_text.txt.gpg"
}];
}
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));
var fname, ctext;