1
0
mirror of https://github.com/QRouland/UTPass.git synced 2025-04-21 21:46:31 +00:00

Compare commits

..

10 Commits

21 changed files with 132 additions and 93 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 black 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

@ -20,10 +20,13 @@ 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 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/
```
@ -44,11 +47,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

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

View File

@ -22,13 +22,13 @@ CloneJob::CloneJob(QString url, QString path, cred_type cred):
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) {
auto ret = this->clone(this->m_url, tmp_dir.absolutePath(), this->m_cred, this->credentialsCB);
if (ret) {
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"));
tmp_dir.removeRecursively();
qDebug() << "Temp dir path is " << tmp_dir.absolutePath();
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();
}
bool CloneJob::moveToDestination(QDir tmp_dir, QString path)
{
qDebug() << "Removing password_store " << path;
qDebug() << "[CloneJob] Removing password_store " << path;
QDir destination_dir(path);
destination_dir.removeRecursively();
qDebug() << "Moving cloned content to destination dir";
qDebug() << "[CloneJob] Moving cloned content to destination 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
}
@ -69,12 +69,17 @@ bool CloneJob::clone(QString url, QString path, cred_type cred, git_cred_acquire
opts.fetch_opts.callbacks.payload = &cred;
int ret = git_clone(&repo, url.toLocal8Bit().data(), path.toLocal8Bit().data(), &opts);
if (ret != 0) {
qDebug() << git_error_last()->message;
if (ret == GIT_EUSER ) {
qDebug() << "[CloneJob] CallBack Error";
} else if (ret != 0) {
auto err = git_error_last(); // TODO Better error handling for return ui messages
if (err) {
qDebug() << "[CloneJob]" << git_error_last()->message;
}
}
if (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.
* @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.

View File

@ -25,27 +25,27 @@ int GitJob::credentialsCB(git_cred **out, const char *url, const char *username_
auto v = overload {
[](const HTTP & x)
{
qDebug() << "credentialsCB : HTTP ";
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;
},
[&out, &username_from_url](const HTTPUserPass & x)
{
qDebug() << "credentialsCB : HTTPUserPass ";
qDebug() << "[GitJob] credentialsCB : HTTPUserPass ";
if (!username_from_url) {
qWarning() << "credentials_cb : no username provided ";
qWarning() << "[GitJob] credentials_cb : no username provided ";
return (int) GIT_EUSER;
}
return git_cred_userpass_plaintext_new(out, username_from_url, x.pass.toLocal8Bit().constData());
},
[](const SSHPass & x)
{
qWarning() << "credentials_cb : SSHAuth to be implemented ";
qWarning() << "[GitJob] credentials_cb : SSHAuth to be implemented ";
return (int) GIT_EUSER;
}, // TODO
[](const SSHKey & x)
{
qWarning() << "credentials_cb : SSHKey to be implemented ";
qWarning() << "[GitJob] credentials_cb : SSHKey to be implemented ";
return (int) GIT_EUSER;
} // TODO
};

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

@ -95,12 +95,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();
@ -109,6 +109,7 @@ bool Pass::deletePasswordStore()
void Pass::slotDeletePasswordStoreResult(bool err)
{
this->initPasswordStore(); // reinit an empty password-store
if (err) {
qInfo() << "[Pass] delete Password Store Failed";
emit deletePasswordStoreFailed("failed to delete password store");

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:
/**
@ -166,15 +166,6 @@ public:
*/
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 +176,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

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

@ -29,7 +29,7 @@ void Utils::unzipResult(bool err)
qDebug() << "Unzip Result";
if (err) {
qInfo() << "Unzip Failed";
emit unzipFailed("failed to unzip archive");
emit unzipFailed();
} else {
qInfo() << "Unzip Succeed";
@ -45,3 +45,14 @@ QString Utils::manifestPath()
qInfo() << "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();
}

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,22 @@ 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);
};
#endif

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: 2025-02-04 17:49+0100\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"
@ -122,8 +122,8 @@ 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/ImportZip.qml:66
#: ../qml/pages/settings/InfoKeys.qml:174
#: ../qml/pages/settings/git/ImportGitClone.qml:56
msgid "Yes"
msgstr ""
@ -137,37 +137,37 @@ msgid "Password Store deleted !"
msgstr ""
#: ../qml/pages/settings/DeleteRepo.qml:90
#: ../qml/pages/settings/InfoKeys.qml:212
#: ../qml/pages/settings/InfoKeys.qml:216
msgid "Info Keys"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:59
#: ../qml/pages/settings/ImportKeyFile.qml:61
msgid "Key import failed !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:68
#: ../qml/pages/settings/ImportKeyFile.qml:70
msgid "Key successfully imported !"
msgstr ""
#: ../qml/pages/settings/ImportKeyFile.qml:79
#: ../qml/pages/settings/ImportKeyFile.qml:81
msgid "GPG Key Import"
msgstr ""
#: ../qml/pages/settings/ImportZip.qml:63
#: ../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/ImportZip.qml:88
#: ../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,27 +179,27 @@ 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 ""

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

@ -14,7 +14,7 @@ Component {
color: theme.palette.normal.background
Text {
text: fileBaseName
text: fileIsDir ? fileName : fileName.slice(0, -4) // remove .gpg if it's a file
anchors.left: parent.left
anchors.leftMargin: units.gu(2)
anchors.verticalCenter: parent.verticalCenter

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

@ -77,8 +77,8 @@ Page {
SuccessDialog {
textSuccess: i18n.tr("Password Store deleted !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
pageStack.clear();
pageStack.push(Qt.resolvedUrl("../PasswordList.qml"));
}
}

View File

@ -30,10 +30,12 @@ Page {
console.log(importKeyFilePage.activeTransfer.items[0].url);
var status = 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(message) {
Utils.rmFile(importKeyFilePage.activeTransfer.items[0].url);
importKeyFilePage.activeTransfer = null;
PopupUtils.open(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
}

View File

@ -77,8 +77,8 @@ Page {
SuccessDialog {
textSuccess: i18n.tr("Password store sucessfully imported !")
onDialogClosed: {
pageStack.pop();
pageStack.pop();
pageStack.clear();
pageStack.push(Qt.resolvedUrl("../../PasswordList.qml"));
}
}