Page MenuHome GnuPG

No OneTemporary

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6971e64..7cbb178 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,42 +1,43 @@
cmake_minimum_required(VERSION 3.16)
set(VERSION "0.0.1")
project(gnupgpass VERSION 0.0.1)
set(QT_MIN_VERSION "6.6.0")
set(KF_MIN_VERSION "5.240.0")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDECMakeSettings)
include(ECMMarkAsTest)
include(ECMAddTests)
include(FeatureSummary)
include(ECMAddAppIcon)
include(ECMSetupVersion)
include(KDEGitCommitHooks)
include(KDEClangFormat)
+kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
# Generate header with version number
ecm_setup_version(${VERSION} VARIABLE_PREFIX GPGPASS VERSION_HEADER
"${CMAKE_CURRENT_BINARY_DIR}/gpgpass_version.h")
find_package(Qt6 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets Test)
include_directories(${CMAKE_BINARY_DIR})
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Prison IconThemes I18n ColorScheme WidgetsAddons)
find_package(QGpgmeQt6 1.19 CONFIG REQUIRED)
add_subdirectory(main)
add_subdirectory(src)
add_subdirectory(tests)
ki18n_install(po)
install(FILES org.gnupg.gpgpass.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.gnupg.gpgpass.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES artwork/sc-gpgpass.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps RENAME org.gnupg.gpgpass.svg)
diff --git a/main/main.cpp b/main/main.cpp
index 7ba50cc..1ba7f8f 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1,81 +1,81 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "firsttimedialog.h"
#include "mainwindow.h"
#include <QApplication>
#include <QDir>
#include <QTranslator>
#include <QtWidgets>
-#include <gpgpass_version.h>
-#include <KIconLoader>
#include <KColorSchemeManager>
+#include <KIconLoader>
#include <KLocalizedString>
+#include <gpgpass_version.h>
/**
* @brief main
* @param argc
* @param argv
* @return
*/
#include <sys/stat.h>
int main(int argc, char *argv[])
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
// allow darkmode
#if defined(Q_OS_WIN)
if (!qEnvironmentVariableIsSet("QT_QPA_PLATFORM")) {
qputenv("QT_QPA_PLATFORM", "windows:darkmode=1");
}
#endif
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain("gpgpass");
Q_INIT_RESOURCE(resources);
QCoreApplication::setOrganizationName(QStringLiteral("GnuPG"));
QCoreApplication::setOrganizationDomain(QStringLiteral("gnupg.org"));
QCoreApplication::setApplicationName(QStringLiteral("GnuPGPass"));
QCoreApplication::setApplicationVersion(QString::fromUtf8(GPGPASS_VERSION_STRING));
#ifdef Q_OS_WINDOWS
QApplication::setWindowIcon(QIcon(QStringLiteral(":/artwork/64-gpgpass.png")));
#else
QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.gnupg.gpgpass")));
#endif
QGuiApplication::setDesktopFileName(QStringLiteral("org.gnupg.gpgpass"));
MainWindow w;
// ensure KIconThemes is loaded for rcc icons
KIconLoader::global()->hasIcon(QString{});
KColorSchemeManager m;
FirstTimeDialog d(&w);
QSettings s;
if (!s.value(QStringLiteral("setup"), false).toBool()) {
if (d.wouldDoSomething()) {
d.show();
} else {
d.doInitialSettings();
w.show();
}
} else {
w.show();
}
return app.exec();
}
diff --git a/src/clipboardhelper.cpp b/src/clipboardhelper.cpp
index d93cc77..88316b8 100644
--- a/src/clipboardhelper.cpp
+++ b/src/clipboardhelper.cpp
@@ -1,87 +1,87 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "clipboardhelper.h"
#include "settings.h"
+#include <KLocalizedString>
#include <QApplication>
#include <QClipboard>
#include <QDialog>
#include <QLabel>
#include <QMainWindow>
#include <QPixmap>
#include <QStatusBar>
#include <QVBoxLayout>
-#include <KLocalizedString>
ClipboardHelper::ClipboardHelper(QMainWindow *mainWindow)
: QObject(mainWindow)
, m_mainWindow(mainWindow)
, clippedText(QString())
{
setClipboardTimer();
clearClipboardTimer.setSingleShot(true);
connect(&clearClipboardTimer, &QTimer::timeout, this, &ClipboardHelper::clearClipboard);
}
ClipboardHelper::~ClipboardHelper() = default;
void ClipboardHelper::setClippedText(const QString &password)
{
clippedText = password;
}
void ClipboardHelper::clearClippedText()
{
clippedText = QString{};
}
void ClipboardHelper::setClipboardTimer()
{
clearClipboardTimer.setInterval(1000 * Settings::getAutoclearSeconds());
}
/**
* Clears the clipboard if we filled it
*/
void ClipboardHelper::clearClipboard()
{
QClipboard *clipboard = QApplication::clipboard();
bool cleared = false;
if (this->clippedText == clipboard->text(QClipboard::Selection)) {
clipboard->clear(QClipboard::Selection);
clipboard->setText(QString{}, QClipboard::Selection);
cleared = true;
}
if (this->clippedText == clipboard->text(QClipboard::Clipboard)) {
clipboard->clear(QClipboard::Clipboard);
cleared = true;
}
if (cleared) {
m_mainWindow->statusBar()->showMessage(i18n("Clipboard cleared"), 2000);
} else {
m_mainWindow->statusBar()->showMessage(i18n("Clipboard not cleared"), 2000);
}
clippedText.clear();
}
/**
* @brief MainWindow::copyTextToClipboard copies text to your clipboard
* @param text
*/
void ClipboardHelper::copyTextToClipboard(const QString &text)
{
QClipboard *clip = QApplication::clipboard();
clip->setText(text, QClipboard::Clipboard);
clippedText = text;
m_mainWindow->statusBar()->showMessage(i18n("Copied to clipboard"), 2000);
if (Settings::isUseAutoclear()) {
clearClipboardTimer.start();
}
}
diff --git a/src/configdialog.cpp b/src/configdialog.cpp
index af75f57..1a4eb28 100644
--- a/src/configdialog.cpp
+++ b/src/configdialog.cpp
@@ -1,332 +1,331 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "configdialog.h"
#include "mainwindow.h"
#include "settings.h"
#include "ui_configdialog.h"
#include "util.h"
+#include <KLocalizedString>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QTableWidgetItem>
#include <gpgpass_version.h>
-#include <KLocalizedString>
/**
* @brief ConfigDialog::ConfigDialog this sets up the configuration screen.
* @param parent
*/
ConfigDialog::ConfigDialog(MainWindow *parent)
: QDialog(parent)
, ui(new Ui::ConfigDialog)
{
mainWindow = parent;
ui->setupUi(this);
ui->profileStorePath->setText(Settings::getPassStore());
ui->currentPathLineEdit->setText(Settings::getPassStore());
ui->spinBoxAutoclearSeconds->setValue(Settings::getAutoclearSeconds());
ui->spinBoxAutoclearPanelSeconds->setValue(Settings::getAutoclearPanelSeconds());
ui->checkBoxHideContent->setChecked(Settings::isHideContent());
ui->checkBoxDisplayAsIs->setChecked(Settings::isDisplayAsIs());
ui->checkBoxNoLineWrapping->setChecked(Settings::isNoLineWrapping());
ui->plainTextEditTemplate->setPlainText(Settings::getPassTemplate());
ui->checkBoxTemplateAllFields->setChecked(Settings::isTemplateAllFields());
setProfiles(Settings::getProfiles(), Settings::getProfile());
setPasswordConfiguration(Settings::getPasswordConfiguration());
useAutoclear(Settings::isUseAutoclear());
useAutoclearPanel(Settings::isUseAutoclearPanel());
validateNewProfile();
useTemplate(Settings::isUseTemplate());
ui->profileTable->verticalHeader()->hide();
ui->profileTable->horizontalHeader()->setStretchLastSection(true);
ui->label->setText(ui->label->text() + QString::fromUtf8(GPGPASS_VERSION_STRING));
// Generic setup
connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted);
// General tab
connect(ui->toolButtonStore, &QAbstractButton::clicked, this, &ConfigDialog::selectStoreFolder);
connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &ConfigDialog::toggleAutoClearPanelSubentries);
connect(ui->checkBoxAutoclear, &QAbstractButton::toggled, this, &ConfigDialog::toggleAutoClearSubentries);
-
// Profiles tab
connect(ui->addButton, &QAbstractButton::clicked, this, &ConfigDialog::addProfile);
connect(ui->deleteButton, &QAbstractButton::clicked, this, &ConfigDialog::deleteSelectedProfile);
connect(ui->createNewProfileFolder, &QAbstractButton::toggled, this, &ConfigDialog::validateNewProfile);
connect(ui->newProfileNameEdit, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile);
connect(ui->profileStorePath, &QLineEdit::textChanged, this, &ConfigDialog::validateNewProfile);
connect(ui->profileTable, &QTableWidget::itemSelectionChanged, this, [this]() {
ui->deleteButton->setEnabled(!ui->profileTable->selectedItems().isEmpty());
});
// template tab
connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &ConfigDialog::toggleTemplateSubentries);
}
bool ConfigDialog::checkNewProfileData()
{
QString name = ui->newProfileNameEdit->text();
if (name.isEmpty()) {
return false;
}
QString path = ui->profileStorePath->text();
if (path.isEmpty()) {
return false;
}
auto nameItem = ui->profileTable->findItems(name, Qt::MatchExactly);
if (!nameItem.empty()) {
return false;
}
auto pathItem = ui->profileTable->findItems(path, Qt::MatchExactly);
if (!pathItem.empty()) {
return false;
}
QFileInfo fi(path);
if (fi.exists()) {
return fi.isDir();
} else {
return ui->createNewProfileFolder->isChecked();
}
}
void ConfigDialog::validateNewProfile()
{
ui->addButton->setEnabled(checkNewProfileData());
}
ConfigDialog::~ConfigDialog() = default;
void ConfigDialog::on_accepted()
{
Settings::setPassStore(Util::normalizeFolderPath(ui->currentPathLineEdit->text()));
Settings::setUseAutoclear(ui->checkBoxAutoclear->isChecked());
Settings::setAutoclearSeconds(ui->spinBoxAutoclearSeconds->value());
Settings::setUseAutoclearPanel(ui->checkBoxAutoclearPanel->isChecked());
Settings::setAutoclearPanelSeconds(ui->spinBoxAutoclearPanelSeconds->value());
Settings::setHideContent(ui->checkBoxHideContent->isChecked());
Settings::setDisplayAsIs(ui->checkBoxDisplayAsIs->isChecked());
Settings::setNoLineWrapping(ui->checkBoxNoLineWrapping->isChecked());
Settings::setProfiles(getProfiles());
Settings::setPasswordConfiguration(getPasswordConfiguration());
Settings::setUseTemplate(ui->checkBoxUseTemplate->isChecked());
Settings::setPassTemplate(ui->plainTextEditTemplate->toPlainText());
Settings::setTemplateAllFields(ui->checkBoxTemplateAllFields->isChecked());
Settings::setVersion(QString::fromUtf8(GPGPASS_VERSION_STRING));
}
/**
* @brief ConfigDialog::selectFolder pop-up to choose a folder.
* @return
*/
QString ConfigDialog::selectFolder()
{
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::Directory);
dialog.setFilter(QDir::NoFilter);
dialog.setOption(QFileDialog::ShowDirsOnly);
if (dialog.exec())
return dialog.selectedFiles().constFirst();
return QString();
}
/**
* @brief ConfigDialog::on_toolButtonStore_clicked get .password-store
* location.s
*/
void ConfigDialog::selectStoreFolder()
{
QString store = selectFolder();
if (!store.isEmpty())
ui->profileStorePath->setText(store);
}
/**
* @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable
* options based on autoclear use.
*/
void ConfigDialog::toggleAutoClearPanelSubentries(bool state)
{
ui->spinBoxAutoclearPanelSeconds->setEnabled(state);
ui->labelPanelSeconds->setEnabled(state);
}
/**
* @brief ConfigDialog::on_checkBoxAutoclearPanel_clicked enable and disable
* options based on autoclear use.
*/
void ConfigDialog::toggleAutoClearSubentries(bool state)
{
ui->spinBoxAutoclearSeconds->setEnabled(state);
}
/**
* @brief ConfigDialog::useAutoclear set the clipboard autoclear use from
* MainWindow.
* @param useAutoclear
*/
void ConfigDialog::useAutoclear(bool useAutoclear)
{
ui->checkBoxAutoclear->setChecked(useAutoclear);
toggleAutoClearSubentries(useAutoclear);
}
/**
* @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from
* MainWindow.
* @param useAutoclearPanel
*/
void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel)
{
ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel);
toggleAutoClearPanelSubentries(useAutoclearPanel);
}
static std::unique_ptr<QTableWidgetItem> createTableWidgetItem(const QString &str)
{
auto item = std::make_unique<QTableWidgetItem>(str);
item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
return item;
}
/**
* @brief ConfigDialog::setProfiles set the profiles and chosen profile from
* MainWindow.
* @param profiles
* @param profile
*/
-void ConfigDialog::setProfiles(const QHash<QString, QString>& profiles, const QString& profile)
+void ConfigDialog::setProfiles(const QHash<QString, QString> &profiles, const QString &profile)
{
ui->profileTable->clear();
ui->profileTable->setSortingEnabled(false);
QHashIterator<QString, QString> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.value().isEmpty() && !i.key().isEmpty()) {
ui->profileTable->insertRow(0);
ui->profileTable->setItem(0, 0, createTableWidgetItem(i.key()).release());
ui->profileTable->setItem(0, 1, createTableWidgetItem(i.value()).release());
}
}
auto selected = ui->profileTable->findItems(profile, Qt::MatchExactly);
if (!selected.empty()) {
ui->profileTable->selectRow(selected.front()->row());
}
ui->profileTable->setSortingEnabled(true);
}
/**
* @brief ConfigDialog::getProfiles return profile list.
* @return
*/
QHash<QString, QString> ConfigDialog::getProfiles()
{
QHash<QString, QString> profiles;
// Check?
for (int i = 0; i < ui->profileTable->rowCount(); ++i) {
QTableWidgetItem *pathItem = ui->profileTable->item(i, 1);
if (nullptr != pathItem) {
QTableWidgetItem *item = ui->profileTable->item(i, 0);
if (item == nullptr) {
continue;
}
profiles.insert(item->text(), pathItem->text());
}
}
return profiles;
}
/**
* @brief ConfigDialog::on_addButton_clicked add a profile row.
*/
void ConfigDialog::addProfile()
{
if (!checkNewProfileData()) {
return;
}
ui->profileTable->setSortingEnabled(false);
int n = ui->profileTable->rowCount();
ui->profileTable->insertRow(n);
ui->profileTable->setItem(n, 0, createTableWidgetItem(ui->newProfileNameEdit->text()).release());
ui->profileTable->setItem(n, 1, createTableWidgetItem(ui->profileStorePath->text()).release());
ui->profileTable->selectRow(n);
ui->deleteButton->setEnabled(true);
ui->profileTable->setSortingEnabled(true);
}
/**
* @brief ConfigDialog::on_deleteButton_clicked remove a profile row.
*/
void ConfigDialog::deleteSelectedProfile()
{
QSet<int> selectedRows; // we use a set to prevent doubles
const QList<QTableWidgetItem *> itemList = ui->profileTable->selectedItems();
if (itemList.count() == 0) {
QMessageBox::warning(this, i18n("No profile selected"), i18n("No profile selected to delete"));
return;
}
ui->profileTable->setSortingEnabled(false);
for (const auto *item : itemList)
selectedRows.insert(item->row());
// get a list, and sort it big to small
QList<int> rows = selectedRows.values();
std::sort(rows.begin(), rows.end());
// now actually do the removing:
for (int row : std::as_const(rows))
ui->profileTable->removeRow(row);
if (ui->profileTable->rowCount() < 1)
ui->deleteButton->setEnabled(false);
ui->profileTable->setSortingEnabled(true);
}
void ConfigDialog::setPasswordConfiguration(const PasswordConfiguration &config)
{
ui->spinBoxPasswordLength->setValue(config.length);
ui->passwordCharTemplateSelector->setCurrentIndex(config.selected);
}
PasswordConfiguration ConfigDialog::getPasswordConfiguration()
{
PasswordConfiguration config;
config.length = ui->spinBoxPasswordLength->value();
config.selected = static_cast<PasswordConfiguration::characterSet>(ui->passwordCharTemplateSelector->currentIndex());
return config;
}
/**
* @brief ConfigDialog::on_checkBoxUseTemplate_clicked enable or disable the
* template field and options.
*/
void ConfigDialog::toggleTemplateSubentries(bool enable)
{
ui->plainTextEditTemplate->setEnabled(enable);
ui->checkBoxTemplateAllFields->setEnabled(enable);
}
/**
* @brief ConfigDialog::useTemplate set preference for using templates.
* @param useTemplate
*/
void ConfigDialog::useTemplate(bool useTemplate)
{
ui->checkBoxUseTemplate->setChecked(useTemplate);
toggleTemplateSubentries(useTemplate);
}
diff --git a/src/configdialog.h b/src/configdialog.h
index 938e702..f5aab4c 100644
--- a/src/configdialog.h
+++ b/src/configdialog.h
@@ -1,67 +1,67 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef CONFIGDIALOG_H_
#define CONFIGDIALOG_H_
#include "passwordconfiguration.h"
#include <QDialog>
namespace Ui
{
struct UserInfo;
class ConfigDialog;
} // namespace Ui
/*!
\class ConfigDialog
\brief The ConfigDialog handles the configuration interface.
This class should also take the handling from the MainWindow class.
*/
class MainWindow;
class QCloseEvent;
class QTableWidgetItem;
class ConfigDialog : public QDialog
{
Q_OBJECT
public:
explicit ConfigDialog(MainWindow *parent);
~ConfigDialog();
private Q_SLOTS:
void on_accepted();
void selectStoreFolder();
void addProfile();
void deleteSelectedProfile();
void toggleAutoClearPanelSubentries(bool enable);
void toggleAutoClearSubentries(bool enable);
void toggleTemplateSubentries(bool enable);
void validateNewProfile();
private:
void useAutoclear(bool useAutoclear);
void useAutoclearPanel(bool useAutoclearPanel);
QHash<QString, QString> getProfiles();
void setPasswordConfiguration(const PasswordConfiguration &config);
PasswordConfiguration getPasswordConfiguration();
void useTemplate(bool useTemplate);
QScopedPointer<Ui::ConfigDialog> ui;
- void setProfiles(const QHash<QString, QString>&, const QString&);
+ void setProfiles(const QHash<QString, QString> &, const QString &);
QString selectFolder();
bool checkNewProfileData();
MainWindow *mainWindow;
};
#endif // CONFIGDIALOG_H_
diff --git a/src/firsttimedialog.cpp b/src/firsttimedialog.cpp
index 370ee1e..9172f5e 100644
--- a/src/firsttimedialog.cpp
+++ b/src/firsttimedialog.cpp
@@ -1,155 +1,155 @@
/*
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "firsttimedialog.h"
#include "qdebug.h"
#include <QPixmap>
#include <QHBoxLayout>
-#include <QPushButton>
#include <QLabel>
+#include <QPushButton>
#include "gpgmehelpers.h"
#include "settings.h"
+#include <KLocalizedString>
#include <QGpgME/KeyListJob>
#include <QProcess>
#include <QStandardPaths>
#include <gpgme++/keylistresult.h>
-#include <KLocalizedString>
QList<UserInfo> DialogState::privateKeys() const
{
auto job = protocol->keyListJob();
std::vector<GpgME::Key> keys;
auto result = job->exec(QStringList(), true, keys);
if (!isSuccess(result.error())) {
return {};
}
QList<UserInfo> users;
for (const auto &key : keys) {
UserInfo ui;
ui.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime());
ui.key_id = fromGpgmeCharStar(key.keyID());
ui.name = createCombinedNameString(key.userID(0));
ui.validity = key.userID(0).validityAsString();
ui.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime());
ui.have_secret = key.hasSecret();
users.append(ui);
}
return users;
}
FirstTimeDialog::FirstTimeDialog(QWidget *mainWindow)
: m_mainWindow(mainWindow)
{
setWindowTitle(i18n("GnuPG Password Manager setup"));
setPage(Intro, new IntroPage(m_state));
QTransform rotate;
rotate.rotate(-90);
setPixmap(QWizard::WatermarkPixmap, QPixmap(QLatin1String(":/artwork/gnupg-logo-320x100tr.png")).transformed(rotate));
setPixmap(QWizard::LogoPixmap, QPixmap(QLatin1String(":/artwork/64-gpgpass.png")));
setStartId(Intro);
}
-void FirstTimeDialog::doInitialSettings() {
+void FirstTimeDialog::doInitialSettings()
+{
QSettings s;
s.setValue(QStringLiteral("setup"), true);
if (Settings::getAutoclearSeconds() < 5)
Settings::setAutoclearSeconds(10);
if (Settings::getAutoclearPanelSeconds() < 5)
Settings::setAutoclearPanelSeconds(10);
Settings::setPassTemplate(QStringLiteral("login\nurl"));
}
void FirstTimeDialog::done(int i)
{
if (i == QDialog::DialogCode::Accepted) {
doInitialSettings();
m_mainWindow->show();
}
QWizard::done(i);
}
-bool FirstTimeDialog::wouldDoSomething() const {
+bool FirstTimeDialog::wouldDoSomething() const
+{
return m_state.privateKeys().isEmpty();
}
int FirstTimeDialog::nextId() const
{
switch (currentId()) {
default:
return -1;
}
};
IntroPage::IntroPage(DialogState &s)
: m_state(s)
, m_kleoPath{QStandardPaths::findExecutable(QStringLiteral("kleopatra"))}
{
{
- QVBoxLayout *lay = new QVBoxLayout();
- lay->addWidget(new QLabel(i18n("No private keys found. Please generate a key:")));
- lay->addWidget(new QLabel(i18n("in a terminal, run `gpg --gen-key` with relevant options")));
- if (!m_kleoPath.isEmpty()) {
- lay->addWidget(new QLabel(i18n("Or launch kleopatra to help create a key")));
- auto startKleo = new QPushButton(i18n("Launch kleopatra"), this);
- connect(startKleo, &QPushButton::clicked, this, [kleoPath = this->m_kleoPath]() {
- QProcess::startDetached(kleoPath, QStringList()
- << QStringLiteral("--gen-key"));
- });
- lay->addWidget(startKleo);
- }
- lay->addWidget(new QLabel(i18n("Please refresh once done")));
- auto refresh = new QPushButton(i18n("Refresh"));
- connect(refresh, &QPushButton::clicked, this, &IntroPage::refresh);
- lay->addWidget(refresh);
-
- m_noKeysContainer = new QWidget;
- m_noKeysContainer->setLayout(lay);
+ QVBoxLayout *lay = new QVBoxLayout();
+ lay->addWidget(new QLabel(i18n("No private keys found. Please generate a key:")));
+ lay->addWidget(new QLabel(i18n("in a terminal, run `gpg --gen-key` with relevant options")));
+ if (!m_kleoPath.isEmpty()) {
+ lay->addWidget(new QLabel(i18n("Or launch kleopatra to help create a key")));
+ auto startKleo = new QPushButton(i18n("Launch kleopatra"), this);
+ connect(startKleo, &QPushButton::clicked, this, [kleoPath = this->m_kleoPath]() {
+ QProcess::startDetached(kleoPath, QStringList() << QStringLiteral("--gen-key"));
+ });
+ lay->addWidget(startKleo);
+ }
+ lay->addWidget(new QLabel(i18n("Please refresh once done")));
+ auto refresh = new QPushButton(i18n("Refresh"));
+ connect(refresh, &QPushButton::clicked, this, &IntroPage::refresh);
+ lay->addWidget(refresh);
+
+ m_noKeysContainer = new QWidget;
+ m_noKeysContainer->setLayout(lay);
}
{
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(new QLabel(i18n("Private keys found. Please close the dialog")));
m_keysContainer = new QWidget;
m_keysContainer->setLayout(lay);
}
QVBoxLayout *lay = new QVBoxLayout();
setTitle(i18n("Welcome"));
setSubTitle(i18n("Key generation"));
lay->addWidget(new QLabel(i18n("Welcome to GnuPG Password manager")));
lay->addWidget(m_noKeysContainer);
lay->addWidget(m_keysContainer);
m_keysContainer->hide();
setLayout(lay);
setFinalPage(true);
}
bool IntroPage::isComplete() const
{
return !m_complete;
}
-void IntroPage::refresh() {
+void IntroPage::refresh()
+{
bool pageComplete = m_state.privateKeys().isEmpty();
if (pageComplete) {
m_noKeysContainer->hide();
m_keysContainer->show();
} else {
m_noKeysContainer->hide();
m_keysContainer->show();
}
if (pageComplete) {
m_complete = pageComplete;
Q_EMIT completeChanged();
}
-
-
}
diff --git a/src/firsttimedialog.h b/src/firsttimedialog.h
index 3d41d0a..eec0bd1 100644
--- a/src/firsttimedialog.h
+++ b/src/firsttimedialog.h
@@ -1,56 +1,57 @@
/*
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef FIRSTTIMEDIALOG_H
#define FIRSTTIMEDIALOG_H
#include "usersdialog.h"
#include <QGpgME/Protocol>
#include <QWizard>
#include <userinfo.h>
class DialogState : public QObject
{
Q_OBJECT
public:
QList<UserInfo> privateKeys() const;
QGpgME::Protocol *protocol = QGpgME::openpgp();
};
class FirstTimeDialog : public QWizard
{
enum Pages { Intro, KeyGen, Done };
Q_OBJECT
public:
FirstTimeDialog(QWidget *mainWindow);
int nextId() const override;
void done(int i) override;
bool wouldDoSomething() const;
void doInitialSettings();
private:
QWidget *m_mainWindow;
DialogState m_state;
};
class IntroPage : public QWizardPage
{
Q_OBJECT
public:
explicit IntroPage(DialogState &s);
bool isComplete() const override;
private Q_SLOTS:
void refresh();
+
private:
DialogState &m_state;
QString m_kleoPath;
bool m_complete = false;
- QWidget* m_noKeysContainer;
- QWidget* m_keysContainer;
+ QWidget *m_noKeysContainer;
+ QWidget *m_keysContainer;
};
#endif // FIRSTTIMEDIALOG_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index fbe822e..48d002a 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,1004 +1,1008 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "mainwindow.h"
#include <gpgpass_version.h>
#include "clipboardhelper.h"
#include "configdialog.h"
#include "filecontent.h"
#include "pass.h"
#include "passworddialog.h"
#include "qpushbuttonfactory.h"
#include "settings.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
-#include <Prison/Barcode>
-#include <KMessageWidget>
#include <KLocalizedString>
+#include <KMessageWidget>
#include <KPasswordLineEdit>
+#include <Prison/Barcode>
#include <QCloseEvent>
+#include <QComboBox>
+#include <QDebug>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
-#include <QTimer>
-#include <QComboBox>
#include <QTextBrowser>
-#include <QDebug>
+#include <QTimer>
static QString directoryName(const QString &dirOrFile)
{
QFileInfo fi{dirOrFile};
if (fi.isDir()) {
return fi.absoluteFilePath();
} else {
return fi.absolutePath();
}
}
/**
* @brief MainWindow::MainWindow handles all of the main functionality and also
* the main window.
* @param searchText for searching from cli
* @param parent pointer
*/
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_pass{std::make_unique<Pass>()}
, ui(new Ui::MainWindow)
, proxyModel{*m_pass}
{
#ifdef __APPLE__
// extra treatment for mac os
// see http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
qt_set_sequence_auto_mnemonic(true);
#endif
ui->setupUi(this);
m_clipboardHelper = new ClipboardHelper(this);
- ui->mainLayout->setContentsMargins(
- style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
- style()->pixelMetric(QStyle::PM_LayoutTopMargin),
- style()->pixelMetric(QStyle::PM_LayoutRightMargin),
- style()->pixelMetric(QStyle::PM_LayoutBottomMargin)
- );
+ ui->mainLayout->setContentsMargins(style()->pixelMetric(QStyle::PM_LayoutLeftMargin),
+ style()->pixelMetric(QStyle::PM_LayoutTopMargin),
+ style()->pixelMetric(QStyle::PM_LayoutRightMargin),
+ style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
m_errorMessage = new KMessageWidget();
m_errorMessage->setMessageType(KMessageWidget::Error);
m_errorMessage->setPosition(KMessageWidget::Position::Header);
m_errorMessage->hide();
ui->messagesArea->addWidget(m_errorMessage);
connect(m_pass.get(), &Pass::errorString, this, [this](auto str) {
m_errorMessage->setText(str);
m_errorMessage->animatedShow();
setUiElementsEnabled(true);
});
connect(m_pass.get(), &Pass::critical, this, &MainWindow::critical);
connect(m_pass.get(), &Pass::finishedShow, this, &MainWindow::passShowHandler);
connect(m_pass.get(), &Pass::finishedInsert, this, [this]() {
this->selectTreeItem(this->getCurrentTreeViewIndex());
});
connect(m_pass.get(), &Pass::startReencryptPath, this, &MainWindow::startReencryptPath);
connect(m_pass.get(), &Pass::endReencryptPath, this, &MainWindow::endReencryptPath);
{
m_notInitialized = new KMessageWidget(i18n("Password store not initialized"));
m_notInitialized->setPosition(KMessageWidget::Position::Header);
m_notInitialized->setCloseButtonVisible(false);
- QAction* action = new QAction(i18n("Initialize with users"));
- connect(action, &QAction::triggered, this, [this](){this->userDialog();});
+ QAction *action = new QAction(i18n("Initialize with users"));
+ connect(action, &QAction::triggered, this, [this]() {
+ this->userDialog();
+ });
m_notInitialized->addAction(action);
m_notInitialized->setMessageType(KMessageWidget::Error);
ui->messagesArea->addWidget(m_notInitialized);
m_notInitialized->hide();
}
{
auto w = new QWidget();
w->setLayout(new QHBoxLayout);
auto l = new QLabel(i18n("Select profile"));
auto sep = ui->toolBar->addSeparator();
w->layout()->addWidget(l);
m_profileBox = new QComboBox();
w->layout()->addWidget(m_profileBox);
m_profiles = ui->toolBar->addWidget(w);
- connect(m_profiles, &QAction::changed, sep, [this, sep]() { sep->setVisible(this->m_profiles->isVisible()); });
+ connect(m_profiles, &QAction::changed, sep, [this, sep]() {
+ sep->setVisible(this->m_profiles->isVisible());
+ });
}
ui->copyPasswordName->hide();
- connect(ui->copyPasswordName, &QPushButton::clicked, this, [this](){
+ connect(ui->copyPasswordName, &QPushButton::clicked, this, [this]() {
m_clipboardHelper->copyTextToClipboard(ui->passwordName->text().trimmed());
});
}
void MainWindow::setVisible(bool visible)
{
// This originated in the ctor, but we want this to happen after the first start wizard has been run
// so for now, moved to show() on first call
if (visible && firstShow) {
firstShow = false;
// register shortcut ctrl/cmd + Q to close the main window
new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q), this, SLOT(close()));
model.setNameFilters(QStringList() << QStringLiteral("*.gpg"));
model.setNameFilterDisables(false);
/*
* I added this to solve Windows bug but now on GNU/Linux the main folder,
* if hidden, disappear
*
* model.setFilter(QDir::NoDot);
*/
auto defaultPassStore = Util::findPasswordStore();
QString passStore = Settings::getPassStore(defaultPassStore);
if (passStore == defaultPassStore) {
// let's write it back
Settings::setPassStore(passStore);
}
QModelIndex rootDir = model.setRootPath(passStore);
model.fetchMore(rootDir);
proxyModel.setSourceModel(&model);
selectionModel.reset(new QItemSelectionModel(&proxyModel));
ui->treeView->setModel(&proxyModel);
ui->treeView->setRootIndex(proxyModel.mapFromSource(rootDir));
ui->treeView->setColumnHidden(1, true);
ui->treeView->setColumnHidden(2, true);
ui->treeView->setColumnHidden(3, true);
ui->treeView->setHeaderHidden(true);
ui->treeView->setIndentation(15);
ui->treeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
ui->treeView->sortByColumn(0, Qt::AscendingOrder);
connect(ui->treeView, &QWidget::customContextMenuRequested, this, &MainWindow::showContextMenu);
connect(ui->treeView, &DeselectableTreeView::emptyClicked, this, &MainWindow::deselect);
updateProfileBox();
clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
clearPanelTimer.setSingleShot(true);
connect(&clearPanelTimer, SIGNAL(timeout()), this, SLOT(clearPanel()));
searchTimer.setInterval(350);
searchTimer.setSingleShot(true);
connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
initToolBarButtons();
initStatusBar();
ui->lineEdit->setClearButtonEnabled(true);
setUiElementsEnabled(true);
QTimer::singleShot(10, this, SLOT(focusInput()));
verifyInitialized();
}
QMainWindow::setVisible(visible);
}
-std::unique_ptr<QTextBrowser> createTextBrowserForNotes() {
+std::unique_ptr<QTextBrowser> createTextBrowserForNotes()
+{
auto textBrowser = std::make_unique<QTextBrowser>();
if (Settings::isNoLineWrapping()) {
textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
}
textBrowser->setOpenExternalLinks(true);
textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu);
textBrowser->document()->setDocumentMargin(0);
return textBrowser;
}
MainWindow::~MainWindow() = default;
/**
* @brief MainWindow::focusInput selects any text (if applicable) in the search
* box and sets focus to it. Allows for easy searching, called at application
* start and when receiving empty message in MainWindow::messageAvailable when
* compiled with SINGLE_APP=1 (default).
*/
void MainWindow::focusInput()
{
ui->lineEdit->selectAll();
ui->lineEdit->setFocus();
}
/**
* @brief MainWindow::changeEvent sets focus to the search box
* @param event
*/
void MainWindow::changeEvent(QEvent *event)
{
QWidget::changeEvent(event);
if (event->type() == QEvent::ActivationChange) {
if (isActiveWindow()) {
focusInput();
}
}
}
/**
* @brief MainWindow::initToolBarButtons init main ToolBar and connect actions
*/
void MainWindow::initToolBarButtons()
{
connect(ui->actionAddPassword, &QAction::triggered, this, &MainWindow::addPassword);
connect(ui->actionAddFolder, &QAction::triggered, this, &MainWindow::addFolder);
connect(ui->actionEdit, &QAction::triggered, this, &MainWindow::onEdit);
connect(ui->actionDelete, &QAction::triggered, this, &MainWindow::onDelete);
connect(ui->actionUsers, &QAction::triggered, this, &MainWindow::onUsers);
connect(ui->actionConfig, &QAction::triggered, this, &MainWindow::onConfig);
connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::selectTreeItem);
connect(ui->treeView, &QTreeView::doubleClicked, this, &MainWindow::editTreeItem);
connect(m_profileBox, &QComboBox::currentTextChanged, this, &MainWindow::selectProfile);
connect(ui->lineEdit, &QLineEdit::textChanged, this, &MainWindow::filterList);
connect(ui->lineEdit, &QLineEdit::returnPressed, this, &MainWindow::selectFromSearch);
ui->actionAddPassword->setIcon(QIcon::fromTheme(QStringLiteral("document-new")));
ui->actionAddFolder->setIcon(QIcon::fromTheme(QStringLiteral("folder-new")));
ui->actionEdit->setIcon(QIcon::fromTheme(QStringLiteral("document-properties")));
ui->actionDelete->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
ui->actionUsers->setIcon(QIcon::fromTheme(QStringLiteral("x-office-address-book")));
ui->actionConfig->setIcon(QIcon::fromTheme(QStringLiteral("configure-symbolic")));
}
/**
* @brief MainWindow::initStatusBar init statusBar with default message and logo
*/
void MainWindow::initStatusBar()
{
- ui->statusBar->showMessage(i18nc("placeholder is version number","Welcome to GnuPG Password Manager %1", QString::fromLocal8Bit(GPGPASS_VERSION_STRING)));
+ ui->statusBar->showMessage(i18nc("placeholder is version number", "Welcome to GnuPG Password Manager %1", QString::fromLocal8Bit(GPGPASS_VERSION_STRING)));
QPixmap logo = QPixmap(QStringLiteral(":/artwork/32-gpgpass.png"));
QLabel *logoApp = new QLabel(statusBar());
logoApp->setPixmap(logo);
statusBar()->addPermanentWidget(logoApp);
}
const QModelIndex MainWindow::getCurrentTreeViewIndex()
{
return ui->treeView->currentIndex();
}
/**
* @brief MainWindow::config pops up the configuration screen and handles all
* inter-window communication
*/
void MainWindow::config()
{
QScopedPointer<ConfigDialog> d(new ConfigDialog(this));
d->setModal(true);
if (d->exec()) {
if (d->result() == QDialog::Accepted) {
this->show();
updateProfileBox();
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
clearPanelTimer.setInterval(1000 * Settings::getAutoclearPanelSeconds());
m_clipboardHelper->setClipboardTimer();
}
}
}
/**
* @brief MainWindow::on_treeView_clicked read the selected password file
* @param index
*/
void MainWindow::selectTreeItem(const QModelIndex &index)
{
bool cleared = ui->treeView->currentIndex().flags() == Qt::NoItemFlags;
// TODO(bezet): "Could not decrypt";
m_clipboardHelper->clearClippedText();
QString file = index.data(QFileSystemModel::FilePathRole).toString();
ui->passwordName->setText(index.data().toString());
ui->copyPasswordName->show();
if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
m_pass->Show(file);
ui->stackedWidget->setCurrentIndex(1);
} else {
clearPanel();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(true);
ui->stackedWidget->setCurrentIndex(0);
}
}
/**
* @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
* TreeViewItem, open the edit Window
* @param index
*/
void MainWindow::editTreeItem(const QModelIndex &index)
{
QFileInfo fileOrFolder{index.data(QFileSystemModel::Roles::FilePathRole).toString()};
if (fileOrFolder.isFile()) {
editPassword(fileOrFolder.absoluteFilePath());
}
}
/**
* @brief MainWindow::deselect clear the selection, password and copy buffer
*/
void MainWindow::deselect()
{
m_clipboardHelper->clearClipboard();
ui->treeView->clearSelection();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
ui->passwordName->setText(QString{});
ui->copyPasswordName->hide();
clearPanel();
ui->stackedWidget->setCurrentIndex(0);
}
void MainWindow::passShowHandler(const QString &p_output)
{
QStringList templ = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QStringLiteral("\n")) : QStringList();
bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields();
FileContent fileContent = FileContent::parse(p_output, templ, allFields);
QString output = p_output;
QString password = fileContent.getPassword();
// set clipped text
m_clipboardHelper->setClippedText(password);
// first clear the current view:
clearPanel();
// show what is needed:
if (Settings::isHideContent()) {
output = i18n("*** Content hidden ***");
} else if (!Settings::isDisplayAsIs()) {
if (!password.isEmpty()) {
// set the password, it is hidden if needed in addToGridLayout
addToGridLayout(i18n("Password:"), password);
}
const NamedValues namedValues = fileContent.getNamedValues();
- for (const auto& nv : namedValues) {
+ for (const auto &nv : namedValues) {
addToGridLayout(i18nc("Field label", "%1:", nv.name), nv.value);
}
output = fileContent.getRemainingDataForDisplay();
}
if (Settings::isUseAutoclearPanel()) {
clearPanelTimer.start();
}
if (!output.isEmpty()) {
output = output.toHtmlEscaped();
output.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
output.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
auto textBrowser = createTextBrowserForNotes();
textBrowser->setHtml(output);
ui->contentLayout->addRow(new QLabel(i18nc("@label", "Additional Notes:")), textBrowser.release());
}
-
setUiElementsEnabled(true);
m_errorMessage->animatedHide();
}
/**
* @brief MainWindow::clearPanel hide the information from shoulder surfers
*/
void MainWindow::clearPanel()
{
clearTemplateWidgets();
ui->passwordName->setText(QString{});
ui->copyPasswordName->hide();
ui->stackedWidget->setCurrentIndex(0);
}
/**
* @brief MainWindow::setUiElementsEnabled enable or disable the relevant UI
* elements
* @param state
*/
void MainWindow::setUiElementsEnabled(bool state)
{
ui->treeView->setEnabled(state);
ui->lineEdit->setEnabled(state);
ui->lineEdit->installEventFilter(this);
ui->actionAddPassword->setEnabled(state);
ui->actionAddFolder->setEnabled(state);
ui->actionUsers->setEnabled(state);
ui->actionConfig->setEnabled(state);
// is a file selected?
state &= ui->treeView->currentIndex().isValid();
ui->actionDelete->setEnabled(state);
ui->actionEdit->setEnabled(state);
}
/**
* @brief MainWindow::on_configButton_clicked run Mainwindow::config
*/
void MainWindow::onConfig()
{
config();
}
/**
* @brief Executes when the string in the search box changes, collapses the
* TreeView
* @param arg1
*/
void MainWindow::filterList(const QString &arg1)
{
ui->statusBar->showMessage(i18n("Looking for: %1", arg1), 1000);
ui->treeView->expandAll();
clearPanel();
ui->passwordName->setText(QString{});
ui->copyPasswordName->hide();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
searchTimer.start();
}
/**
* @brief MainWindow::onTimeoutSearch Fired when search is finished or too much
* time from two keypresses is elapsed
*/
void MainWindow::onTimeoutSearch()
{
QString query = ui->lineEdit->text();
if (query.isEmpty()) {
ui->treeView->collapseAll();
deselect();
}
query.replace(QStringLiteral(" "), QStringLiteral(".*"));
QRegularExpression regExp(query, QRegularExpression::CaseInsensitiveOption);
proxyModel.setFilterRegularExpression(regExp);
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
if (proxyModel.rowCount() > 0 && !query.isEmpty()) {
selectFirstFile();
} else {
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
}
}
/**
* @brief MainWindow::on_lineEdit_returnPressed get searching
*
* Select the first possible file in the tree
*/
void MainWindow::selectFromSearch()
{
if (proxyModel.rowCount() > 0) {
selectFirstFile();
selectTreeItem(ui->treeView->currentIndex());
}
}
/**
* @brief MainWindow::selectFirstFile select the first possible file in the
* tree
*/
void MainWindow::selectFirstFile()
{
QModelIndex index = proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore()));
index = firstFile(index);
ui->treeView->setCurrentIndex(index);
}
/**
* @brief MainWindow::firstFile return location of first possible file
* @param parentIndex
* @return QModelIndex
*/
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
QModelIndex index = parentIndex;
int numRows = proxyModel.rowCount(parentIndex);
for (int row = 0; row < numRows; ++row) {
index = proxyModel.index(row, 0, parentIndex);
if (model.fileInfo(proxyModel.mapToSource(index)).isFile())
return index;
if (proxyModel.hasChildren(index))
return firstFile(index);
}
return index;
}
/**
* @brief MainWindow::setPassword open passworddialog
* @param file which pgp file
* @param isNew insert (not update)
*/
void MainWindow::setPassword(QString file, bool isNew)
{
PasswordDialog d(*m_pass, file, isNew, this);
if (!d.exec()) {
ui->treeView->setFocus();
}
}
/**
* @brief MainWindow::addPassword add a new password by showing a
* number of dialogs.
*/
void MainWindow::addPassword()
{
bool ok;
QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString());
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QString file = QInputDialog::getText(this, i18n("New file"), i18n("New password file: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok);
if (!ok || file.isEmpty())
return;
file = QDir(dir).absoluteFilePath(file + QStringLiteral(".gpg"));
setPassword(file);
}
/**
* @brief MainWindow::onDelete remove password, if you are
* sure.
*/
void MainWindow::onDelete()
{
QModelIndex currentIndex = ui->treeView->currentIndex();
if (!currentIndex.isValid()) {
// If not valid, we might end up passing empty string
// to delete, and taht might delete unexpected things on disk
return;
}
QFileInfo fileOrFolder{currentIndex.data(QFileSystemModel::FilePathRole).toString()};
bool isDir = fileOrFolder.isDir();
QString file = fileOrFolder.absoluteFilePath();
QString message;
if (isDir) {
- message = i18nc("deleting a folder; placeholder is folder name","Are you sure you want to delete %1 and the whole content?", file);
+ message = i18nc("deleting a folder; placeholder is folder name", "Are you sure you want to delete %1 and the whole content?", file);
QDirIterator it(model.rootPath() + QLatin1Char('/') + file, QDirIterator::Subdirectories);
while (it.hasNext()) {
it.next();
if (auto fi = it.fileInfo(); fi.isFile()) {
if (fi.suffix() != QStringLiteral("gpg")) {
- message += QStringLiteral("<br><strong>") + i18nc("extra warning during certain folder deletions","Attention: "
- "there are unexpected files in the given folder, "
- "check them before continue") + QStringLiteral("</strong>");
+ message += QStringLiteral("<br><strong>")
+ + i18nc("extra warning during certain folder deletions",
+ "Attention: "
+ "there are unexpected files in the given folder, "
+ "check them before continue")
+ + QStringLiteral("</strong>");
break;
}
}
}
} else {
- message = i18nc("deleting a file; placeholder is file name","Are you sure you want to delete %1?", file);
+ message = i18nc("deleting a file; placeholder is file name", "Are you sure you want to delete %1?", file);
}
- if (QMessageBox::question(this,
- isDir ? i18n("Delete folder?") : i18n("Delete password?"),
- message,
- QMessageBox::Yes | QMessageBox::No)
- != QMessageBox::Yes)
+ if (QMessageBox::question(this, isDir ? i18n("Delete folder?") : i18n("Delete password?"), message, QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
m_pass->Remove(file, isDir);
}
/**
* @brief MainWindow::onEdit try and edit (selected) password.
*/
void MainWindow::onEdit()
{
QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
if (!file.isEmpty()) {
editPassword(file);
}
}
/**
* @brief MainWindow::userDialog see MainWindow::onUsers()
* @param dir folder to edit users for.
*/
void MainWindow::userDialog(QString dir)
{
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
UsersDialog d(dir, *m_pass, this);
if (!d.exec()) {
ui->treeView->setFocus();
}
verifyInitialized();
}
/**
* @brief MainWindow::onUsers edit users for the current
* folder,
* gets lists and opens UserDialog.
*/
void MainWindow::onUsers()
{
QString dir = ui->treeView->currentIndex().data(QFileSystemModel::Roles::FilePathRole).toString();
if (dir.isEmpty()) {
dir = Settings::getPassStore();
} else {
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
dir = Util::normalizeFolderPath(dir);
}
userDialog(dir);
}
/**
* @brief MainWindow::updateProfileBox update the list of profiles, optionally
* select a more appropriate one to view too
*/
void MainWindow::updateProfileBox()
{
QHash<QString, QString> profiles = Settings::getProfiles();
if (profiles.isEmpty()) {
m_profiles->setVisible(false);
} else {
m_profiles->setVisible(true);
m_profileBox->setEnabled(profiles.size() > 1);
m_profileBox->clear();
QHashIterator<QString, QString> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.key().isEmpty())
m_profileBox->addItem(i.key());
}
m_profileBox->model()->sort(0);
}
int index = m_profileBox->findText(Settings::getProfile());
if (index != -1) // -1 for not found
m_profileBox->setCurrentIndex(index);
}
/**
* @brief MainWindow::on_profileBox_currentIndexChanged make sure we show the
* correct "profile"
* @param name
*/
void MainWindow::selectProfile(QString name)
{
if (name == Settings::getProfile())
return;
ui->lineEdit->clear();
clearPanel();
Settings::setProfile(name);
Settings::setPassStore(Settings::getProfiles().value(name));
ui->statusBar->showMessage(i18n("Profile changed to %1", name), 2000);
ui->treeView->selectionModel()->clear();
ui->treeView->setRootIndex(proxyModel.mapFromSource(model.setRootPath(Settings::getPassStore())));
verifyInitialized();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
}
-void MainWindow::verifyInitialized() {
+void MainWindow::verifyInitialized()
+{
bool actionsEnabled;
if (!QFile::exists(Settings::getPassStore() + QStringLiteral("/.gpg-id"))) {
m_notInitialized->animatedShow();
actionsEnabled = false;
} else {
m_notInitialized->animatedHide();
actionsEnabled = true;
}
ui->actionAddFolder->setEnabled(actionsEnabled);
ui->actionAddPassword->setEnabled(actionsEnabled);
ui->actionDelete->setEnabled(ui->actionDelete->isEnabled() && actionsEnabled);
ui->actionEdit->setEnabled(ui->actionEdit->isEnabled() && actionsEnabled);
}
/**
* @brief MainWindow::closeEvent hide or quit
* @param event
*/
void MainWindow::closeEvent(QCloseEvent *event)
{
m_clipboardHelper->clearClipboard();
event->accept();
}
/**
* @brief MainWindow::eventFilter filter out some events and focus the
* treeview
* @param obj
* @param event
* @return
*/
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == ui->lineEdit && event->type() == QEvent::KeyPress) {
auto *key = dynamic_cast<QKeyEvent *>(event);
if (key != nullptr && key->key() == Qt::Key_Down) {
ui->treeView->setFocus();
}
}
return QObject::eventFilter(obj, event);
}
/**
* @brief MainWindow::keyPressEvent did anyone press return, enter or escape?
* @param event
*/
void MainWindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Delete:
onDelete();
break;
case Qt::Key_Return:
case Qt::Key_Enter:
if (proxyModel.rowCount() > 0)
selectTreeItem(ui->treeView->currentIndex());
break;
case Qt::Key_Escape:
ui->lineEdit->clear();
break;
default:
break;
}
}
/**
* @brief MainWindow::showContextMenu show us the (file or folder) context
* menu
* @param pos
*/
void MainWindow::showContextMenu(const QPoint &pos)
{
QModelIndex index = ui->treeView->indexAt(pos);
bool selected = true;
if (!index.isValid()) {
ui->treeView->clearSelection();
ui->actionDelete->setEnabled(false);
ui->actionEdit->setEnabled(false);
selected = false;
}
ui->treeView->setCurrentIndex(index);
QPoint globalPos = ui->treeView->viewport()->mapToGlobal(pos);
QFileInfo fileOrFolder = model.fileInfo(proxyModel.mapToSource(ui->treeView->currentIndex()));
QMenu contextMenu;
if (!selected || fileOrFolder.isDir()) {
- const QAction *addFolderAction = contextMenu.addAction(i18n("Add folder"));
+ const QAction *addFolderAction = contextMenu.addAction(i18n("Add folder"));
const QAction *addPasswordAction = contextMenu.addAction(i18n("Add password"));
const QAction *usersAction = contextMenu.addAction(i18n("Users"));
connect(addFolderAction, &QAction::triggered, this, &MainWindow::addFolder);
connect(addPasswordAction, &QAction::triggered, this, &MainWindow::addPassword);
connect(usersAction, &QAction::triggered, this, &MainWindow::onUsers);
} else if (fileOrFolder.isFile()) {
const QAction *edit = contextMenu.addAction(i18n("Edit"));
connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
}
if (selected) {
contextMenu.addSeparator();
if (fileOrFolder.isDir()) {
const QAction *renameFolderAction = contextMenu.addAction(i18n("Rename folder"));
connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
} else if (fileOrFolder.isFile()) {
const QAction *renamePasswordAction = contextMenu.addAction(i18n("Rename password"));
connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
}
const QAction *deleteItem = contextMenu.addAction(i18n("Delete"));
connect(deleteItem, &QAction::triggered, this, &MainWindow::onDelete);
}
contextMenu.exec(globalPos);
}
/**
* @brief MainWindow::addFolder add a new folder to store passwords in
*/
void MainWindow::addFolder()
{
bool ok;
QString dir = directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString());
if (dir.isEmpty()) {
dir = Settings::getPassStore();
}
QString newdir = QInputDialog::getText(this, i18n("New file"), i18n("New Folder: \n(Will be placed in %1 )", dir), QLineEdit::Normal, QString{}, &ok);
if (!ok || newdir.isEmpty())
return;
QDir(dir).mkdir(newdir);
}
/**
* @brief MainWindow::renameFolder rename an existing folder
*/
void MainWindow::renameFolder()
{
bool ok;
QString srcDir = QDir::cleanPath(directoryName(ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString()));
if (srcDir.isEmpty()) {
return;
}
QString srcDirName = QDir(srcDir).dirName();
QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename Folder To: "), QLineEdit::Normal, srcDirName, &ok);
if (!ok || newName.isEmpty())
return;
QString destDir = srcDir;
destDir.replace(srcDir.lastIndexOf(srcDirName), srcDirName.length(), newName);
m_pass->Move(srcDir, destDir);
}
/**
* @brief MainWindow::editPassword read password and open edit window via
* MainWindow::onEdit()
*/
void MainWindow::editPassword(const QString &file)
{
if (!file.isEmpty()) {
setPassword(file, false);
}
}
/**
* @brief MainWindow::renamePassword rename an existing password
*/
void MainWindow::renamePassword()
{
bool ok;
QString file = ui->treeView->currentIndex().data(QFileSystemModel::FilePathRole).toString();
QString filePath = QFileInfo(file).path();
QString fileName = QFileInfo(file).fileName();
if (fileName.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
fileName.chop(4);
QString newName = QInputDialog::getText(this, i18n("Rename file"), i18n("Rename File To: "), QLineEdit::Normal, fileName, &ok);
if (!ok || newName.isEmpty())
return;
QString newFile = QDir(filePath).filePath(newName);
m_pass->Move(file, newFile);
}
/**
* @brief MainWindow::clearTemplateWidgets empty the template widget fields in
* the UI
*/
void MainWindow::clearTemplateWidgets()
{
while (ui->contentLayout->count() > 0) {
- ui->contentLayout->removeRow(ui->contentLayout->rowCount() -1);
+ ui->contentLayout->removeRow(ui->contentLayout->rowCount() - 1);
}
}
/**
* @brief MainWindow::addToGridLayout add a field to the template grid
* @param position
* @param field
* @param value
*/
void MainWindow::addToGridLayout(const QString &field, const QString &value)
{
QString trimmedField = field.trimmed();
QString trimmedValue = value.trimmed();
// Combine the Copy button and the line edit in one widget
auto rowLayout = new QHBoxLayout();
rowLayout->setContentsMargins(0, 2, 0, 2);
if (trimmedField == i18n("Password:")) {
auto *line = new KPasswordLineEdit();
line->setRevealPasswordMode(KPassword::RevealMode::Always);
line->setObjectName(trimmedField);
line->setPassword(trimmedValue);
line->setReadOnly(true);
line->setContentsMargins(0, 0, 0, 0);
line->setEchoMode(QLineEdit::Password);
auto icon = QIcon::fromTheme(QStringLiteral("password-show-on"));
icon.addFile(QStringLiteral("password-show-off"), QSize(), QIcon::Normal, QIcon::Off);
rowLayout->addWidget(line);
} else {
auto *line = new QLabel();
line->setOpenExternalLinks(true);
line->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
line->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
line->setObjectName(trimmedField);
trimmedValue.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
line->setText(trimmedValue);
line->setContentsMargins(5, 0, 0, 0);
rowLayout->addWidget(line);
}
auto fieldName = trimmedField;
fieldName.removeLast(); // remove ':' from the end of the label
- auto fieldLabel = createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy '%1' to clipboard", fieldName), m_clipboardHelper, [this, trimmedValue] {
- m_clipboardHelper->copyTextToClipboard(trimmedValue);
- });
+ auto fieldLabel =
+ createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("Copy '%1' to clipboard", fieldName), m_clipboardHelper, [this, trimmedValue] {
+ m_clipboardHelper->copyTextToClipboard(trimmedValue);
+ });
rowLayout->addWidget(fieldLabel.release());
- auto qrButton = createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), i18n("View '%1' QR Code", fieldName), m_clipboardHelper, [this, trimmedValue]() {
- auto barcode = Prison::Barcode::create(Prison::QRCode);
- if (!barcode) {
- return;
- }
- barcode->setData(trimmedValue);
- auto image = barcode->toImage(barcode->preferredSize(window()->devicePixelRatioF()));
-
- QDialog popup(nullptr, Qt::Popup | Qt::FramelessWindowHint);
- QVBoxLayout *layout = new QVBoxLayout;
- QLabel *popupLabel = new QLabel();
- layout->addWidget(popupLabel);
- popupLabel->setPixmap(QPixmap::fromImage(image));
- popupLabel->setScaledContents(true);
- popupLabel->show();
- popup.setLayout(layout);
- popup.move(QCursor::pos());
- popup.exec();
- });
+ auto qrButton =
+ createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), i18n("View '%1' QR Code", fieldName), m_clipboardHelper, [this, trimmedValue]() {
+ auto barcode = Prison::Barcode::create(Prison::QRCode);
+ if (!barcode) {
+ return;
+ }
+ barcode->setData(trimmedValue);
+ auto image = barcode->toImage(barcode->preferredSize(window()->devicePixelRatioF()));
+
+ QDialog popup(nullptr, Qt::Popup | Qt::FramelessWindowHint);
+ QVBoxLayout *layout = new QVBoxLayout;
+ QLabel *popupLabel = new QLabel();
+ layout->addWidget(popupLabel);
+ popupLabel->setPixmap(QPixmap::fromImage(image));
+ popupLabel->setScaledContents(true);
+ popupLabel->show();
+ popup.setLayout(layout);
+ popup.move(QCursor::pos());
+ popup.exec();
+ });
rowLayout->addWidget(qrButton.release());
// set into the layout
ui->contentLayout->addRow(trimmedField, rowLayout);
}
/**
* @brief MainWindow::startReencryptPath disable ui elements and treeview
*/
void MainWindow::startReencryptPath()
{
statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
setUiElementsEnabled(false);
ui->treeView->setDisabled(true);
}
/**
* @brief MainWindow::endReencryptPath re-enable ui elements
*/
void MainWindow::endReencryptPath()
{
setUiElementsEnabled(true);
}
/**
* @brief MainWindow::critical critical message popup wrapper.
* @param title
* @param msg
*/
-void MainWindow::critical(const QString& title, const QString& msg)
+void MainWindow::critical(const QString &title, const QString &msg)
{
QMessageBox::critical(this, title, msg);
}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 47f529d..adea1f5 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1,130 +1,130 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2016-2017 tezeb <tezeb+github@outoftheblue.pl>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_
#include "storemodel.h"
#include <QFileSystemModel>
#include <QItemSelectionModel>
#include <QMainWindow>
#include <QProcess>
#include <QTimer>
#ifdef __APPLE__
// http://doc.qt.io/qt-5/qkeysequence.html#qt_set_sequence_auto_mnemonic
void qt_set_sequence_auto_mnemonic(bool b);
#endif
namespace Ui
{
class MainWindow;
}
/*!
\class MainWindow
\brief The MainWindow class does way too much, not only is it a switchboard,
configuration handler and more, it's also the process-manager.
This class could really do with an overhaul.
*/
class QComboBox;
class ClipboardHelper;
class Pass;
class KMessageWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void restoreWindow();
void userDialog(QString = {});
void config();
void setUiElementsEnabled(bool state);
const QModelIndex getCurrentTreeViewIndex();
void setVisible(bool visible) override;
protected:
void closeEvent(QCloseEvent *event) override;
void keyPressEvent(QKeyEvent *event) override;
void changeEvent(QEvent *event) override;
bool eventFilter(QObject *obj, QEvent *event) override;
public Q_SLOTS:
void deselect();
- void critical(const QString&, const QString&);
+ void critical(const QString &, const QString &);
void passShowHandler(const QString &);
void selectTreeItem(const QModelIndex &index);
void startReencryptPath();
void endReencryptPath();
private Q_SLOTS:
void addPassword();
void addFolder();
void onEdit();
void onDelete();
void onUsers();
void onConfig();
void editTreeItem(const QModelIndex &index);
void clearPanel();
void filterList(const QString &arg1);
void selectFromSearch();
void selectProfile(QString);
void showContextMenu(const QPoint &pos);
void renameFolder();
void editPassword(const QString &);
void renamePassword();
void focusInput();
void onTimeoutSearch();
void verifyInitialized();
private:
std::unique_ptr<Pass> m_pass;
ClipboardHelper *m_clipboardHelper;
QScopedPointer<Ui::MainWindow> ui;
QFileSystemModel model;
StoreModel proxyModel;
QScopedPointer<QItemSelectionModel> selectionModel;
QTimer clearPanelTimer, searchTimer;
- KMessageWidget* m_notInitialized;
- KMessageWidget* m_errorMessage;
- QAction* m_profiles;
- QComboBox* m_profileBox;
+ KMessageWidget *m_notInitialized;
+ KMessageWidget *m_errorMessage;
+ QAction *m_profiles;
+ QComboBox *m_profileBox;
bool firstShow = true;
void initToolBarButtons();
void initStatusBar();
void updateText();
void selectFirstFile();
QModelIndex firstFile(QModelIndex parentIndex);
void setPassword(QString, bool isNew = true);
void updateProfileBox();
void initTrayIcon();
void destroyTrayIcon();
void clearTemplateWidgets();
void reencryptPath(QString dir);
void addToGridLayout(const QString &field, const QString &value);
};
#endif // MAINWINDOW_H_
diff --git a/src/pass.cpp b/src/pass.cpp
index 53d8e82..6eff292 100644
--- a/src/pass.cpp
+++ b/src/pass.cpp
@@ -1,437 +1,441 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2017 Jason A. Donenfeld <Jason@zx2c4.com>
SPDX-FileCopyrightText: 2020 Charlie Waters <cawiii@me.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "pass.h"
#include "gpgmehelpers.h"
#include "settings.h"
+#include <KLocalizedString>
+#include <QDebug>
#include <QDir>
#include <QDirIterator>
#include <QGpgME/DecryptJob>
#include <QGpgME/EncryptJob>
#include <QGpgME/KeyListJob>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QSaveFile>
#include <gpgme++/decryptionresult.h>
#include <gpgme++/encryptionresult.h>
#include <gpgme++/keygenerationresult.h>
#include <gpgme++/keylistresult.h>
-#include <QDebug>
-#include <KLocalizedString>
// TODO remove
#include <QMessageBox>
using namespace std;
// TODO remove
Q_REQUIRED_RESULT static bool ensurePassStore(const QString &file, const char *location)
{
if (file.startsWith(Settings::getPassStore())) {
return true;
}
- QMessageBox::critical(nullptr, QStringLiteral("error"), QStringLiteral("file not in pass store called from %1 %2 %3").arg(QString::fromLocal8Bit(location),file,Settings::getPassStore()));
+ QMessageBox::critical(nullptr,
+ QStringLiteral("error"),
+ QStringLiteral("file not in pass store called from %1 %2 %3").arg(QString::fromLocal8Bit(location), file, Settings::getPassStore()));
return false;
}
// TODO remove
Q_REQUIRED_RESULT static bool ensureSuffix(const QString &file, const char *location)
{
if (file.endsWith(QStringLiteral(".gpg"))) {
return true;
}
qWarning() << "file without suffix called from " << location;
- QMessageBox::critical(nullptr, QStringLiteral("error"), QStringLiteral("file without suffix called from %1 %2").arg(QString::fromLocal8Bit(location),file));
+ QMessageBox::critical(nullptr,
+ QStringLiteral("error"),
+ QStringLiteral("file without suffix called from %1 %2").arg(QString::fromLocal8Bit(location), file));
return false;
}
/**
* @brief Pass::Pass wrapper for using either pass or the pass imitation
*/
Pass::Pass()
{
}
/**
* @brief Pass::Generate use either pwgen or internal password
* generator
* @param length of the desired password
* @param charset to use for generation
* @return the password
*/
QString Pass::Generate_b(unsigned int length, const QString &charset)
{
if (charset.length() > 0) {
return generateRandomPassword(charset, length);
} else {
Q_EMIT critical(i18n("No characters chosen"),
i18n("Can't generate password, there are no characters to choose from "
- "set in the configuration!"));
+ "set in the configuration!"));
}
return {};
}
/**
* @brief Pass::listKeys list users
* @param keystrings
* @param secret list private keys
* @return QList<UserInfo> users
*/
-QList<UserInfo> Pass::listKeys(const QStringList& keystrings, bool secret)
+QList<UserInfo> Pass::listKeys(const QStringList &keystrings, bool secret)
{
auto job = protocol->keyListJob();
std::vector<GpgME::Key> keys;
job->addMode(GpgME::KeyListMode::WithSecret);
auto result = job->exec(keystrings, secret, keys);
if (!isSuccess(result.error())) {
return {};
}
QList<UserInfo> users;
for (const auto &key : keys) {
UserInfo ui;
ui.created = QDateTime::fromSecsSinceEpoch(key.subkey(0).creationTime());
ui.key_id = fromGpgmeCharStar(key.keyID());
ui.name = createCombinedNameString(key.userID(0));
ui.validity = key.userID(0).validityAsString();
ui.expiry = QDateTime::fromSecsSinceEpoch(key.subkey(0).expirationTime());
ui.have_secret = key.hasSecret();
users.append(ui);
}
return users;
}
/**
* @brief Pass::listKeys list users
* @param keystring
* @param secret list private keys
* @return QList<UserInfo> users
*/
-QList<UserInfo> Pass::listKeys(const QString& keystring, bool secret)
+QList<UserInfo> Pass::listKeys(const QString &keystring, bool secret)
{
return listKeys(QStringList(keystring), secret);
}
/**
* @brief Pass::getRecipientList return list of gpg-id's to encrypt for
* @param for_file which file (folder) would you like recepients for
* @return recepients gpg-id contents
*/
-QStringList Pass::getRecipientList(const QString& for_file)
+QStringList Pass::getRecipientList(const QString &for_file)
{
if (!ensurePassStore(for_file, Q_FUNC_INFO)) {
return {};
}
QDir gpgIdPath(QFileInfo(for_file).absoluteDir());
bool found = false;
while (gpgIdPath.exists() && gpgIdPath.absolutePath().startsWith(Settings::getPassStore())) {
if (QFile(gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id"))).exists()) {
found = true;
break;
}
if (!gpgIdPath.cdUp())
break;
}
QFile gpgId(found ? gpgIdPath.absoluteFilePath(QStringLiteral(".gpg-id")) : Settings::getPassStore() + QStringLiteral(".gpg-id"));
if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text))
return QStringList();
QStringList recipients;
while (!gpgId.atEnd()) {
QString recipient(QString::fromLocal8Bit(gpgId.readLine()));
recipient = recipient.trimmed();
if (!recipient.isEmpty())
recipients += recipient;
}
return recipients;
}
-void Pass::Show(const QString& file)
+void Pass::Show(const QString &file)
{
- if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file,Q_FUNC_INFO))) {
- return;
+ if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file, Q_FUNC_INFO))) {
+ return;
}
QFile f(file);
f.open(QIODevice::ReadOnly);
auto data = f.readAll();
auto job = protocol->decryptJob();
connect(job, &QGpgME::DecryptJob::result, this, [this](auto &&result, QByteArray plainText, QString auditLog, auto &&logError) {
if (isSuccess(result.error())) {
Q_EMIT finishedShow(QString::fromUtf8(plainText));
} else {
Q_EMIT errorString(QString::fromLocal8Bit(result.error().asString()));
Q_EMIT decryptionError();
}
// Q_EMIT log(auditLog);
Q_UNUSED(logError);
Q_UNUSED(auditLog);
});
job->start(data);
}
-void Pass::Insert(const QString& file, const QString& newValue)
+void Pass::Insert(const QString &file, const QString &newValue)
{
- if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file,Q_FUNC_INFO))) {
- return;
+ if (!(ensureSuffix(file, Q_FUNC_INFO) && ensurePassStore(file, Q_FUNC_INFO))) {
+ return;
}
QStringList recipients = Pass::getRecipientList(file);
auto job = protocol->encryptJob();
std::vector<GpgME::Key> keys;
auto ctx = QGpgME::Job::context(job);
for (const auto &keyId : recipients) {
GpgME::Error error;
auto key = ctx->key(keyId.toUtf8().data(), error, false);
if (!error && !key.isNull()) {
keys.push_back(key);
}
}
if (keys.empty()) {
Q_EMIT critical(i18n("Can not edit"),
i18n("Could not read encryption key to use, .gpg-id "
- "file missing or invalid."));
+ "file missing or invalid."));
return;
}
connect(job, &QGpgME::EncryptJob::result, this, [this, file](auto &&result, const QByteArray &ciphertext, const QString &log, auto &&auditResult) {
if (isSuccess(result.error())) {
QSaveFile f(file);
bool open = f.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!open) {
Q_EMIT errorString(QStringLiteral("File open failed: %1").arg(f.errorString()));
return;
}
f.write(ciphertext);
if (f.error() == QFileDevice::NoError) {
f.commit();
Q_EMIT finishedInsert();
} else {
Q_EMIT errorString(QStringLiteral("File write failed: %1").arg(f.errorString()));
}
} else {
Q_EMIT errorString(QString::fromUtf8(result.error().asString()));
}
Q_UNUSED(log);
Q_UNUSED(auditResult);
});
job->start(keys, newValue.toUtf8());
}
-void Pass::Remove(const QString& file, bool isDir)
+void Pass::Remove(const QString &file, bool isDir)
{
if (!ensurePassStore(file, Q_FUNC_INFO)) {
return;
}
if (!isDir) {
- if(!ensureSuffix(file, Q_FUNC_INFO)) {
+ if (!ensureSuffix(file, Q_FUNC_INFO)) {
return;
}
QFile(file).remove();
} else {
QDir dir(file);
dir.removeRecursively();
}
}
-void Pass::Init(const QString& path, const QList<UserInfo> &users)
+void Pass::Init(const QString &path, const QList<UserInfo> &users)
{
if (!ensurePassStore(path, Q_FUNC_INFO)) {
return;
}
QString gpgIdFile = path + QStringLiteral(".gpg-id");
QFile gpgId(gpgIdFile);
if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
Q_EMIT critical(i18n("Cannot update"), i18n("Failed to open .gpg-id for writing."));
return;
}
bool secret_selected = false;
for (const UserInfo &user : users) {
if (user.enabled) {
gpgId.write((user.key_id + QStringLiteral("\n")).toUtf8());
secret_selected |= user.have_secret;
}
}
gpgId.close();
if (!secret_selected) {
Q_EMIT critical(i18n("Check selected users!"),
i18n("None of the selected keys have a secret key available.\n"
- "You will not be able to decrypt any newly added passwords!"));
+ "You will not be able to decrypt any newly added passwords!"));
return;
}
reencryptPath(path);
}
/**
* @brief ImitatePass::reencryptPath reencrypt all files under the chosen
* directory
*
* This is stil quite experimental..
* @param dir
*/
void Pass::reencryptPath(const QString &dir)
{
if (!ensurePassStore(dir, Q_FUNC_INFO)) {
return;
}
Q_EMIT startReencryptPath();
QDir currentDir;
QDirIterator gpgFiles(dir, QStringList() << QStringLiteral("*.gpg"), QDir::Files, QDirIterator::Subdirectories);
QStringList gpgId;
while (gpgFiles.hasNext()) {
QString fileName = gpgFiles.next();
if (gpgFiles.fileInfo().path() != currentDir.path()) {
gpgId = getRecipientList(fileName);
gpgId.sort();
}
QByteArray plainText;
QFile f(fileName);
if (!f.open(QIODevice::ReadOnly)) {
Q_EMIT errorString(QStringLiteral("Failed to open file: %1").arg(fileName));
continue;
}
QByteArray cipherText = f.readAll();
f.close();
auto decryptJob = protocol->decryptJob();
auto context = QGpgME::Job::context(decryptJob);
auto decryptResult = decryptJob->exec(cipherText, plainText);
if (!isSuccess(decryptResult.error())) {
Q_EMIT errorString(fromGpgmeCharStar(decryptResult.error().asString()));
continue;
}
auto actualRecipients = decryptResult.recipients();
QStringList actualIds;
for (auto &recipient : actualRecipients) {
GpgME::Error error;
auto key = context->key(recipient.keyID(), error, false);
if (!error) {
actualIds.append(fromGpgmeCharStar(key.keyID()));
}
}
actualIds.sort();
if (actualIds != gpgId) {
auto encryptJob = protocol->encryptJob();
std::vector<GpgME::Key> keys;
auto ctx = QGpgME::Job::context(encryptJob);
for (const auto &keyId : std::as_const(gpgId)) {
GpgME::Error error;
auto key = ctx->key(keyId.toUtf8().data(), error, false);
if (!error && !key.isNull()) {
keys.push_back(key);
}
}
auto encryptResult = encryptJob->exec(keys, plainText, false, cipherText);
if (!isSuccess(encryptResult.error())) {
Q_EMIT errorString(fromGpgmeCharStar(encryptResult.error().asString()));
continue;
}
QSaveFile save(fileName);
bool open = save.open(QIODevice::WriteOnly | QIODevice::Truncate);
if (!open) {
Q_EMIT errorString(QStringLiteral("open file failed %1").arg(fileName));
continue;
}
save.write(cipherText);
if (save.error() != QFileDevice::NoError) {
Q_EMIT errorString(QStringLiteral("Writing file failed: %1").arg(fileName));
continue;
} else {
save.commit();
}
}
}
Q_EMIT endReencryptPath();
}
-void Pass::Move(const QString& src, const QString& dest, const bool force)
+void Pass::Move(const QString &src, const QString &dest, const bool force)
{
QFileInfo srcFileInfo(src);
QFileInfo destFileInfo(dest);
QString destFile;
QString srcFileBaseName = srcFileInfo.fileName();
if (srcFileInfo.isFile()) {
if (destFileInfo.isFile()) {
if (!force) {
return;
}
} else if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else {
destFile = dest;
}
if (destFile.endsWith(QStringLiteral(".gpg"), Qt::CaseInsensitive))
destFile.chop(4); // make sure suffix is lowercase
destFile.append(QStringLiteral(".gpg"));
} else if (srcFileInfo.isDir()) {
if (destFileInfo.isDir()) {
destFile = QDir(dest).filePath(srcFileBaseName);
} else if (destFileInfo.isFile()) {
return;
} else {
destFile = dest;
}
} else {
return;
}
QDir qDir;
if (force) {
qDir.remove(destFile);
}
qDir.rename(src, destFile);
}
-void Pass::Copy(const QString& src, const QString& dest, const bool force)
+void Pass::Copy(const QString &src, const QString &dest, const bool force)
{
QFileInfo destFileInfo(dest);
QDir qDir;
if (force) {
qDir.remove(dest);
}
if (!QFile::copy(src, dest)) {
Q_EMIT errorString(QStringLiteral("Failed to copy file"));
}
// reecrypt all files under the new folder
if (destFileInfo.isDir()) {
reencryptPath(destFileInfo.absoluteFilePath());
} else if (destFileInfo.isFile()) {
reencryptPath(destFileInfo.dir().path());
}
}
quint32 Pass::boundedRandom(quint32 bound)
{
if (bound < 2) {
return 0;
}
quint32 randval;
const quint32 max_mod_bound = (1 + ~bound) % bound;
do {
randval = QRandomGenerator::system()->generate();
} while (randval < max_mod_bound);
return randval % bound;
}
QString Pass::generateRandomPassword(const QString &charset, unsigned int length)
{
QString out;
for (unsigned int i = 0; i < length; ++i) {
out.append(charset.at(static_cast<int>(boundedRandom(static_cast<quint32>(charset.length())))));
}
return out;
}
diff --git a/src/pass.h b/src/pass.h
index 53e599c..47fe58d 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -1,63 +1,63 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2017 Jason A. Donenfeld <Jason@zx2c4.com>
SPDX-FileCopyrightText: 2020 Charlie Waters <cawiii@me.com>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef PASS_H
#define PASS_H
#include "userinfo.h"
#include <QGpgME/Protocol>
#include <QQueue>
#include <QString>
#include <cassert>
#include <map>
/*!
\class Pass
\brief Acts as an abstraction for pass or pass imitation
*/
class Pass final : public QObject
{
Q_OBJECT
public:
Pass();
- void Show(const QString& file);
- void Insert(const QString& file, const QString& value);
- void Remove(const QString& file, bool isDir);
- void Move(const QString& srcDir, const QString& dest, const bool force = false);
- void Copy(const QString& srcDir, const QString& dest, const bool force = false);
+ void Show(const QString &file);
+ void Insert(const QString &file, const QString &value);
+ void Remove(const QString &file, bool isDir);
+ void Move(const QString &srcDir, const QString &dest, const bool force = false);
+ void Copy(const QString &srcDir, const QString &dest, const bool force = false);
void Init(const QString &path, const QList<UserInfo> &users);
QString Generate_b(unsigned int length, const QString &charset);
- QList<UserInfo> listKeys(const QStringList& keystrings, bool secret = false);
- QList<UserInfo> listKeys(const QString& keystring = QString{}, bool secret = false);
- static QStringList getRecipientList(const QString& for_file);
+ QList<UserInfo> listKeys(const QStringList &keystrings, bool secret = false);
+ QList<UserInfo> listKeys(const QString &keystring = QString{}, bool secret = false);
+ static QStringList getRecipientList(const QString &for_file);
private:
QGpgME::Protocol *protocol = QGpgME::openpgp();
QString generateRandomPassword(const QString &charset, unsigned int length);
quint32 boundedRandom(quint32 bound);
void reencryptPath(const QString &dir);
Q_SIGNALS:
void startReencryptPath();
void endReencryptPath();
void errorString(const QString &err);
- void critical(const QString&, const QString&);
+ void critical(const QString &, const QString &);
// messages are provided via errorString
void decryptionError();
void finishedShow(const QString &plainTextPassword);
void finishedInsert();
};
#endif // PASS_H
diff --git a/src/passworddialog.cpp b/src/passworddialog.cpp
index 2a599df..4fe29e3 100644
--- a/src/passworddialog.cpp
+++ b/src/passworddialog.cpp
@@ -1,254 +1,254 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "passworddialog.h"
#include "filecontent.h"
#include "pass.h"
#include "passwordconfiguration.h"
#include "settings.h"
#include "ui_passworddialog.h"
+#include <QFileInfo>
#include <QLabel>
#include <QLineEdit>
-#include <QFileInfo>
/**
* @brief PasswordDialog::PasswordDialog basic constructor.
* @param passConfig configuration constant
* @param parent
*/
PasswordDialog::PasswordDialog(Pass &pass, const PasswordConfiguration &passConfig, QWidget *parent)
: QDialog(parent)
, ui(std::make_unique<Ui::PasswordDialog>())
, m_pass(pass)
, m_passConfig(passConfig)
{
m_templating = false;
m_allFields = false;
m_isNew = false;
ui->setupUi(this);
setLength(m_passConfig.length);
setPasswordCharTemplate(m_passConfig.selected);
connect(&m_pass, &Pass::finishedShow, this, &PasswordDialog::setPass);
connect(ui->createPasswordOptionsButton, &QAbstractButton::toggled, this, &PasswordDialog::togglePasswordGenerationOption);
togglePasswordGenerationOption(false);
}
/**
* @brief PasswordDialog::PasswordDialog complete constructor.
* @param file
* @param isNew
* @param parent pointer
*/
PasswordDialog::PasswordDialog(Pass &pass, const QString &file, const bool &isNew, QWidget *parent)
: QDialog(parent)
, ui(std::make_unique<Ui::PasswordDialog>())
, m_pass(pass)
, m_file(file)
, m_isNew(isNew)
{
if (!isNew)
m_pass.Show(m_file);
ui->setupUi(this);
setWindowTitle(this->windowTitle() + QStringLiteral(" ") + QFileInfo(m_file).baseName());
m_passConfig = Settings::getPasswordConfiguration();
setTemplate(Settings::getPassTemplate(), Settings::isUseTemplate());
templateAll(Settings::isTemplateAllFields());
setLength(m_passConfig.length);
setPasswordCharTemplate(m_passConfig.selected);
connect(&m_pass, &Pass::finishedShow, this, &PasswordDialog::setPass);
connect(&m_pass, &Pass::decryptionError, this, &PasswordDialog::close);
connect(this, &PasswordDialog::accepted, this, &PasswordDialog::dialogAccepted);
connect(this, &PasswordDialog::rejected, this, &PasswordDialog::dialogCancelled);
connect(ui->createPasswordButton, &QAbstractButton::clicked, this, &PasswordDialog::createPassword);
connect(ui->createPasswordOptionsButton, &QAbstractButton::toggled, this, &PasswordDialog::togglePasswordGenerationOption);
togglePasswordGenerationOption(false);
}
/**
* @brief Pass{}{}wordDialog::~PasswordDialog basic destructor.
*/
PasswordDialog::~PasswordDialog() = default;
/**
* @brief PasswordDialog::on_createPasswordButton_clicked generate a random
* passwords.
* @todo refactor when process is untangled from MainWindow class.
*/
void PasswordDialog::createPassword()
{
setEnabled(false);
QString newPass = m_pass.Generate_b(static_cast<unsigned int>(ui->spinBox_pwdLength->value()),
m_passConfig.Characters[static_cast<PasswordConfiguration::characterSet>(ui->passwordTemplateSwitch->currentIndex())]);
if (newPass.length() > 0)
ui->lineEditPassword->setPassword(newPass);
setEnabled(true);
}
/**
* @brief PasswordDialog::on_accepted handle Ok click for QDialog
*/
void PasswordDialog::dialogAccepted()
{
QString newValue = getPassword();
if (newValue.isEmpty())
return;
if (newValue.right(1) != QLatin1Char('\n'))
newValue += QLatin1Char('\n');
m_pass.Insert(m_file, newValue);
}
/**
* @brief PasswordDialog::on_rejected handle Cancel click for QDialog
*/
void PasswordDialog::dialogCancelled()
{
setPassword(QString());
}
/**
* @brief PasswordDialog::setPassword populate the (templated) fields.
* @param password
*/
-void PasswordDialog::setPassword(const QString& password)
+void PasswordDialog::setPassword(const QString &password)
{
FileContent fileContent = FileContent::parse(password, m_templating ? m_fields : QStringList(), m_allFields);
ui->lineEditPassword->setPassword(fileContent.getPassword());
QWidget *previous = ui->createPasswordButton;
// first set templated values
NamedValues namedValues = fileContent.getNamedValues();
for (QLineEdit *line : std::as_const(templateLines)) {
line->setText(namedValues.takeValue(line->objectName()));
previous = line;
}
// show remaining values (if there are)
otherLines.clear();
for (const NamedValue &nv : std::as_const(namedValues)) {
auto *line = new QLineEdit();
line->setObjectName(nv.name);
line->setText(nv.value);
ui->formLayout->addRow(new QLabel(nv.name), line);
setTabOrder(previous, line);
otherLines.append(line);
previous = line;
}
ui->plainTextEdit->insertPlainText(fileContent.getRemainingData());
}
/**
* @brief PasswordDialog::getPassword join the (templated) fields to a QString
* for writing back.
* @return collappsed password.
*/
QString PasswordDialog::getPassword()
{
QString passFile = ui->lineEditPassword->password() + QLatin1Char('\n');
QList<QLineEdit *> allLines(templateLines);
allLines.append(otherLines);
for (QLineEdit *line : allLines) {
QString text = line->text();
if (text.isEmpty())
continue;
passFile += line->objectName() + QStringLiteral(": ") + text + QLatin1Char('\n');
}
passFile += ui->plainTextEdit->toPlainText();
return passFile;
}
/**
* @brief PasswordDialog::setTemplate set the template and create the fields.
* @param rawFields
*/
-void PasswordDialog::setTemplate(const QString& rawFields, bool useTemplate)
+void PasswordDialog::setTemplate(const QString &rawFields, bool useTemplate)
{
m_fields = rawFields.split(QLatin1Char('\n'));
m_templating = useTemplate;
templateLines.clear();
if (m_templating) {
QWidget *previous = ui->createPasswordButton;
for (const QString &field : std::as_const(m_fields)) {
if (field.isEmpty())
continue;
auto *line = new QLineEdit();
line->setObjectName(field);
ui->formLayout->addRow(new QLabel(i18nc("Field name", "%1:", field)), line);
setTabOrder(previous, line);
templateLines.append(line);
previous = line;
}
}
}
/**
* @brief PasswordDialog::templateAll basic setter for use in
* PasswordDialog::setPassword templating all tokenisable lines.
* @param templateAll
*/
void PasswordDialog::templateAll(bool templateAll)
{
m_allFields = templateAll;
}
/**
* @brief PasswordDialog::setLength
* PasswordDialog::setLength password length.
* @param l
*/
void PasswordDialog::setLength(int l)
{
ui->spinBox_pwdLength->setValue(l);
}
/**
* @brief PasswordDialog::setPasswordCharTemplate
* PasswordDialog::setPasswordCharTemplate chose the template style.
* @param t
*/
void PasswordDialog::setPasswordCharTemplate(int t)
{
ui->passwordTemplateSwitch->setCurrentIndex(t);
}
void PasswordDialog::setPass(const QString &output)
{
setPassword(output);
// TODO(bezet): enable ui
}
void PasswordDialog::togglePasswordGenerationOption(bool checked)
{
if (checked) {
ui->createPasswordOptionsButton->setIcon(QIcon::fromTheme(QStringLiteral("collapse-symbolic")));
ui->createPasswordOptionsButton->setToolTip(i18nc("@info:tooltip", "Collapse password options"));
ui->createPasswordOptionsButton->setAccessibleName(i18nc("@info:tooltip", "Collapse password options"));
ui->label_characterset->setVisible(true);
ui->passwordTemplateSwitch->setVisible(true);
ui->label_length->setVisible(true);
ui->spinBox_pwdLength->setVisible(true);
} else {
ui->createPasswordOptionsButton->setIcon(QIcon::fromTheme(QStringLiteral("expand-symbolic")));
ui->createPasswordOptionsButton->setToolTip(i18nc("@info:tooltip", "Toggle password options"));
ui->createPasswordOptionsButton->setAccessibleName(i18nc("@info:tooltip", "Toggle password options"));
ui->label_characterset->setVisible(false);
ui->passwordTemplateSwitch->setVisible(false);
ui->label_length->setVisible(false);
ui->spinBox_pwdLength->setVisible(false);
}
}
diff --git a/src/passworddialog.h b/src/passworddialog.h
index 2b8644c..d2b8c34 100644
--- a/src/passworddialog.h
+++ b/src/passworddialog.h
@@ -1,84 +1,84 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef PASSWORDDIALOG_H_
#define PASSWORDDIALOG_H_
#include "passwordconfiguration.h"
#include <QDialog>
namespace Ui
{
class PasswordDialog;
}
class QLineEdit;
class QWidget;
class Pass;
/*!
\class PasswordDialog
\brief PasswordDialog Handles the inserting and editing of passwords.
Includes templated views.
*/
class PasswordDialog : public QDialog
{
Q_OBJECT
public:
explicit PasswordDialog(Pass &pass, const PasswordConfiguration &passConfig, QWidget *parent = nullptr);
PasswordDialog(Pass &pass, const QString &file, const bool &isNew, QWidget *parent = nullptr);
~PasswordDialog();
/*! Sets content in the password field in the interface.
\param password the password as a QString
\sa getPassword
*/
- void setPassword(const QString& password);
+ void setPassword(const QString &password);
/*! Returns the password as set in the password field in the interface.
\return password as a QString
\sa setPassword
*/
QString getPassword();
/*! Sets content in the template for the interface.
\param rawFields is the template as a QString
\param useTemplate whether the template is used
*/
- void setTemplate(const QString& rawFields, bool useTemplate);
+ void setTemplate(const QString &rawFields, bool useTemplate);
void templateAll(bool templateAll);
void setLength(int l);
void setPasswordCharTemplate(int t);
public Q_SLOTS:
void setPass(const QString &output);
private Q_SLOTS:
void createPassword();
void dialogAccepted();
void dialogCancelled();
void togglePasswordGenerationOption(bool checked);
private:
std::unique_ptr<Ui::PasswordDialog> ui;
Pass &m_pass;
PasswordConfiguration m_passConfig;
QStringList m_fields;
QString m_file;
bool m_templating;
bool m_allFields;
bool m_isNew;
QList<QLineEdit *> templateLines;
QList<QLineEdit *> otherLines;
};
#endif // PASSWORDDIALOG_H_
diff --git a/src/qpushbuttonfactory.h b/src/qpushbuttonfactory.h
index 62c3a44..791f572 100644
--- a/src/qpushbuttonfactory.h
+++ b/src/qpushbuttonfactory.h
@@ -1,25 +1,25 @@
/*
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef QPUSHBUTTONFACTORY_H_
#define QPUSHBUTTONFACTORY_H_
#include <QLineEdit>
#include <QPushButton>
#include <memory>
template<typename ReceiverObject, typename Callable>
-std::unique_ptr<QPushButton> createPushButton(const QIcon &icon, const QString& accessibleName, ReceiverObject *onClickedReceiver, Callable target)
+std::unique_ptr<QPushButton> createPushButton(const QIcon &icon, const QString &accessibleName, ReceiverObject *onClickedReceiver, Callable target)
{
static_assert(std::is_base_of<QObject, ReceiverObject>::value, "Receiver should inherit from QObject");
auto button = std::make_unique<QPushButton>(icon, QString{});
button->setAccessibleName(accessibleName);
button->setToolTip(accessibleName);
button->connect(button.get(), &QPushButton::clicked, onClickedReceiver, target);
return button;
}
#endif // QPUSHBUTTONFACTORY_H_
diff --git a/src/settings.cpp b/src/settings.cpp
index 242027a..6b96a4e 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -1,247 +1,247 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Lukas Vogel <lukedirtwalker@gmail.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2022 Tobias Leupold <tl@l3u.de>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "settings.h"
#include <QCoreApplication>
#include <QDir>
#include <QFile>
#include <QString>
/*!
\class SettingsConstants
\brief Table for the naming of configuration items
*/
namespace SettingsConstants
{
- const QString version = QStringLiteral("version");
- const QString useAutoclear = QStringLiteral("useAutoclear");
- const QString autoclearSeconds = QStringLiteral("autoclearSeconds");
- const QString useAutoclearPanel = QStringLiteral("useAutoclearPanel");
- const QString autoclearPanelSeconds = QStringLiteral("autoclearPanelSeconds");
- const QString hideContent = QStringLiteral("hideContent");
- const QString displayAsIs = QStringLiteral("displayAsIs");
- const QString noLineWrapping = QStringLiteral("noLineWrapping");
- const QString passStore = QStringLiteral("passStore");
- const QString profile = QStringLiteral("profile");
- const QString groupProfiles = QStringLiteral("profiles");
- const QString avoidCapitals = QStringLiteral("avoidCapitals");
- const QString avoidNumbers = QStringLiteral("avoidNumbers");
- const QString passwordLength = QStringLiteral("passwordLength");
- const QString passwordCharsselection = QStringLiteral("passwordCharsselection");
- const QString passTemplate = QStringLiteral("passTemplate");
- const QString useTemplate = QStringLiteral("useTemplate");
- const QString templateAllFields = QStringLiteral("templateAllFields");
+const QString version = QStringLiteral("version");
+const QString useAutoclear = QStringLiteral("useAutoclear");
+const QString autoclearSeconds = QStringLiteral("autoclearSeconds");
+const QString useAutoclearPanel = QStringLiteral("useAutoclearPanel");
+const QString autoclearPanelSeconds = QStringLiteral("autoclearPanelSeconds");
+const QString hideContent = QStringLiteral("hideContent");
+const QString displayAsIs = QStringLiteral("displayAsIs");
+const QString noLineWrapping = QStringLiteral("noLineWrapping");
+const QString passStore = QStringLiteral("passStore");
+const QString profile = QStringLiteral("profile");
+const QString groupProfiles = QStringLiteral("profiles");
+const QString avoidCapitals = QStringLiteral("avoidCapitals");
+const QString avoidNumbers = QStringLiteral("avoidNumbers");
+const QString passwordLength = QStringLiteral("passwordLength");
+const QString passwordCharsselection = QStringLiteral("passwordCharsselection");
+const QString passTemplate = QStringLiteral("passTemplate");
+const QString useTemplate = QStringLiteral("useTemplate");
+const QString templateAllFields = QStringLiteral("templateAllFields");
};
bool Settings::initialized = false;
Settings *Settings::m_instance = nullptr;
Settings *Settings::getInstance()
{
if (!Settings::initialized) {
QString portable_ini = QCoreApplication::applicationDirPath() + QStringLiteral("/gnupgpass.ini");
if (QFile(portable_ini).exists()) {
m_instance = new Settings(portable_ini, QSettings::IniFormat);
} else {
m_instance = new Settings();
}
initialized = true;
}
return m_instance;
}
PasswordConfiguration Settings::getPasswordConfiguration()
{
PasswordConfiguration config;
config.length = getInstance()->value(SettingsConstants::passwordLength, 0).toInt();
config.selected = static_cast<PasswordConfiguration::characterSet>(getInstance()->value(SettingsConstants::passwordCharsselection, 0).toInt());
return config;
}
void Settings::setPasswordConfiguration(const PasswordConfiguration &config)
{
getInstance()->setValue(SettingsConstants::passwordLength, config.length);
getInstance()->setValue(SettingsConstants::passwordCharsselection, config.selected);
}
QHash<QString, QString> Settings::getProfiles()
{
getInstance()->beginGroup(SettingsConstants::profile);
const QStringList childrenKeys = getInstance()->childKeys();
QHash<QString, QString> profiles;
for (const QString &key : childrenKeys) {
profiles.insert(key, getInstance()->value(key).toString());
}
getInstance()->endGroup();
return profiles;
}
void Settings::setProfiles(const QHash<QString, QString> &profiles)
{
getInstance()->remove(SettingsConstants::profile);
getInstance()->beginGroup(SettingsConstants::profile);
QHash<QString, QString>::const_iterator i = profiles.begin();
for (; i != profiles.end(); ++i) {
getInstance()->setValue(i.key(), i.value());
}
getInstance()->endGroup();
}
QString Settings::getVersion(const QString &defaultValue)
{
return getInstance()->value(SettingsConstants::version, defaultValue).toString();
}
void Settings::setVersion(const QString &version)
{
getInstance()->setValue(SettingsConstants::version, version);
}
bool Settings::isUseAutoclear(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::useAutoclear, defaultValue).toBool();
}
void Settings::setUseAutoclear(const bool &useAutoclear)
{
getInstance()->setValue(SettingsConstants::useAutoclear, useAutoclear);
}
int Settings::getAutoclearSeconds(const int &defaultValue)
{
return getInstance()->value(SettingsConstants::autoclearSeconds, defaultValue).toInt();
}
void Settings::setAutoclearSeconds(const int &autoClearSeconds)
{
getInstance()->setValue(SettingsConstants::autoclearSeconds, autoClearSeconds);
}
bool Settings::isUseAutoclearPanel(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::useAutoclearPanel, defaultValue).toBool();
}
void Settings::setUseAutoclearPanel(const bool &useAutoclearPanel)
{
getInstance()->setValue(SettingsConstants::useAutoclearPanel, useAutoclearPanel);
}
int Settings::getAutoclearPanelSeconds(const int &defaultValue)
{
return getInstance()->value(SettingsConstants::autoclearPanelSeconds, defaultValue).toInt();
}
void Settings::setAutoclearPanelSeconds(const int &autoClearPanelSeconds)
{
getInstance()->setValue(SettingsConstants::autoclearPanelSeconds, autoClearPanelSeconds);
}
bool Settings::isHideContent(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::hideContent, defaultValue).toBool();
}
void Settings::setHideContent(const bool &hideContent)
{
getInstance()->setValue(SettingsConstants::hideContent, hideContent);
}
bool Settings::isDisplayAsIs(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::displayAsIs, defaultValue).toBool();
}
void Settings::setDisplayAsIs(const bool &displayAsIs)
{
getInstance()->setValue(SettingsConstants::displayAsIs, displayAsIs);
}
bool Settings::isNoLineWrapping(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::noLineWrapping, defaultValue).toBool();
}
void Settings::setNoLineWrapping(const bool &noLineWrapping)
{
getInstance()->setValue(SettingsConstants::noLineWrapping, noLineWrapping);
}
QString Settings::getPassStore(const QString &defaultValue)
{
QString returnValue = getInstance()->value(SettingsConstants::passStore, defaultValue).toString();
// Normalize the path string
returnValue = QDir(returnValue).absolutePath();
// ensure directory exists if never used pass or misconfigured.
// otherwise process->setWorkingDirectory(passStore); will fail on execution.
if (!QDir(returnValue).exists()) {
QDir().mkdir(returnValue);
}
// ensure path ends in /
if (!returnValue.endsWith(QLatin1Char('/'))) {
returnValue += QLatin1Char('/');
}
return returnValue;
}
void Settings::setPassStore(const QString &passStore)
{
getInstance()->setValue(SettingsConstants::passStore, passStore);
}
QString Settings::getProfile(const QString &defaultValue)
{
return getInstance()->value(SettingsConstants::profile, defaultValue).toString();
}
void Settings::setProfile(const QString &profile)
{
getInstance()->setValue(SettingsConstants::profile, profile);
}
void Settings::setPasswordLength(const int &passwordLength)
{
getInstance()->setValue(SettingsConstants::passwordLength, passwordLength);
}
void Settings::setPasswordCharsselection(const int &passwordCharsselection)
{
getInstance()->setValue(SettingsConstants::passwordCharsselection, passwordCharsselection);
}
QString Settings::getPassTemplate(const QString &defaultValue)
{
return getInstance()->value(SettingsConstants::passTemplate, defaultValue).toString();
}
void Settings::setPassTemplate(const QString &passTemplate)
{
getInstance()->setValue(SettingsConstants::passTemplate, passTemplate);
}
bool Settings::isUseTemplate(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::useTemplate, defaultValue).toBool();
}
void Settings::setUseTemplate(const bool &useTemplate)
{
getInstance()->setValue(SettingsConstants::useTemplate, useTemplate);
}
bool Settings::isTemplateAllFields(const bool &defaultValue)
{
return getInstance()->value(SettingsConstants::templateAllFields, defaultValue).toBool();
}
void Settings::setTemplateAllFields(const bool &templateAllFields)
{
getInstance()->setValue(SettingsConstants::templateAllFields, templateAllFields);
}
diff --git a/src/storemodel.cpp b/src/storemodel.cpp
index f6a380b..2b7cad0 100644
--- a/src/storemodel.cpp
+++ b/src/storemodel.cpp
@@ -1,277 +1,278 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "storemodel.h"
#include "pass.h"
#include "util.h"
#include <QDebug>
#include <QFileSystemModel>
#include <QMessageBox>
#include <QMimeData>
#include <QRegularExpression>
-#include <utility>
#include <klocalizedstring.h>
+#include <utility>
static const QString mimeType = QStringLiteral("application/vnd+gnupgpass.dragAndDropInfoPasswordStore");
QDataStream &operator<<(QDataStream &out, const dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
out << dragAndDropInfoPasswordStore.isDir << dragAndDropInfoPasswordStore.isFile << dragAndDropInfoPasswordStore.path;
return out;
}
QDataStream &operator>>(QDataStream &in, dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
in >> dragAndDropInfoPasswordStore.isDir >> dragAndDropInfoPasswordStore.isFile >> dragAndDropInfoPasswordStore.path;
return in;
}
/**
* @brief StoreModel::StoreModel
* SubClass of QSortFilterProxyModel via
* http://www.qtcentre.org/threads/46471-QTreeView-Filter
*/
StoreModel::StoreModel(Pass &pass)
: m_pass(pass)
{
setRecursiveFilteringEnabled(true);
setAutoAcceptChildRows(true);
}
/**
* @brief StoreModel::data don't show the .gpg at the end of a file.
* @param index
* @param role
* @return
*/
QVariant StoreModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
auto initial_value = QSortFilterProxyModel::data(index, role);
if (role == Qt::DisplayRole) {
QString name = initial_value.toString();
name.replace(Util::endsWithGpg(), QString{});
return name;
}
return initial_value;
}
/**
* @brief StoreModel::supportedDropActions enable drop.
* @return
*/
Qt::DropActions StoreModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
/**
* @brief StoreModel::supportedDragActions enable drag.
* @return
*/
Qt::DropActions StoreModel::supportedDragActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
/**
* @brief StoreModel::flags
* @param index
* @return
*/
Qt::ItemFlags StoreModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);
if (index.isValid()) {
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
}
return Qt::ItemIsDropEnabled | defaultFlags;
}
/**
* @brief StoreModel::mimeTypes
* @return
*/
QStringList StoreModel::mimeTypes() const
{
QStringList types;
types << mimeType;
return types;
}
/**
* @brief StoreModel::mimeData
* @param indexes
* @return
*/
QMimeData *StoreModel::mimeData(const QModelIndexList &indexes) const
{
dragAndDropInfoPasswordStore info;
QByteArray encodedData;
// only use the first, otherwise we should enable multiselection
QModelIndex index = indexes.at(0);
if (index.isValid()) {
QModelIndex useIndex = mapToSource(index);
info.isDir = fs()->fileInfo(useIndex).isDir();
info.isFile = fs()->fileInfo(useIndex).isFile();
info.path = fs()->fileInfo(useIndex).absoluteFilePath();
QDataStream stream(&encodedData, QIODevice::WriteOnly);
stream << info;
}
auto *mimeData = new QMimeData();
mimeData->setData(mimeType, encodedData);
return mimeData;
}
/**
* @brief StoreModel::canDropMimeData
* @param data
* @param action
* @param row
* @param column
* @param parent
* @return
*/
bool StoreModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
#ifdef QT_DEBUG
qDebug() << action << row;
#else
Q_UNUSED(action)
Q_UNUSED(row)
#endif
if (!fs()) {
return false;
}
QModelIndex useIndex = this->index(parent.row(), parent.column(), parent.parent());
QByteArray encodedData = data->data(mimeType);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
dragAndDropInfoPasswordStore info;
stream >> info;
if (!data->hasFormat(mimeType))
return false;
if (column > 0) {
return false;
}
// you can drop a folder on a folder
if (fs()->fileInfo(mapToSource(useIndex)).isDir() && info.isDir) {
return true;
}
// you can drop a file on a folder
if (fs()->fileInfo(mapToSource(useIndex)).isDir() && info.isFile) {
return true;
}
// you can drop a file on a file
if (fs()->fileInfo(mapToSource(useIndex)).isFile() && info.isFile) {
return true;
}
return false;
}
/**
* @brief StoreModel::dropMimeData
* @param data
* @param action
* @param row
* @param column
* @param parent
* @return
*/
bool StoreModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction) {
return true;
}
QByteArray encodedData = data->data(mimeType);
QDataStream stream(&encodedData, QIODevice::ReadOnly);
dragAndDropInfoPasswordStore info;
stream >> info;
QModelIndex destIndex = this->index(parent.row(), parent.column(), parent.parent());
QFileInfo destFileinfo = fs()->fileInfo(mapToSource(destIndex));
QFileInfo srcFileInfo = QFileInfo(info.path);
QString cleanedSrc = QDir::cleanPath(srcFileInfo.absoluteFilePath());
QString cleanedDest = QDir::cleanPath(destFileinfo.absoluteFilePath());
if (info.isDir) {
// dropped dir onto dir
if (destFileinfo.isDir()) {
QDir destDir = QDir(cleanedDest).filePath(srcFileInfo.fileName());
QString cleanedDestDir = QDir::cleanPath(destDir.absolutePath());
if (action == Qt::MoveAction) {
m_pass.Move(cleanedSrc, cleanedDestDir);
} else if (action == Qt::CopyAction) {
m_pass.Copy(cleanedSrc, cleanedDestDir);
}
}
} else if (info.isFile) {
// dropped file onto a directory
if (destFileinfo.isDir()) {
if (action == Qt::MoveAction) {
m_pass.Move(cleanedSrc, cleanedDest);
} else if (action == Qt::CopyAction) {
m_pass.Copy(cleanedSrc, cleanedDest);
}
} else if (destFileinfo.isFile()) {
// dropped file onto a file
int answer = QMessageBox::question(nullptr,
i18n("Force overwrite?"),
- i18nc("Overwrite DestinationFile with SourceFile","Overwrite %1 with %2?", cleanedDest, cleanedSrc),
+ i18nc("Overwrite DestinationFile with SourceFile", "Overwrite %1 with %2?", cleanedDest, cleanedSrc),
QMessageBox::Yes | QMessageBox::No);
bool force = answer == QMessageBox::Yes;
if (action == Qt::MoveAction) {
m_pass.Move(cleanedSrc, cleanedDest, force);
} else if (action == Qt::CopyAction) {
m_pass.Copy(cleanedSrc, cleanedDest, force);
}
}
}
return true;
}
/**
* @brief StoreModel::lessThan
* @param source_left
* @param source_right
* @return
*/
bool StoreModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
/* matches logic in QFileSystemModelSorter::compareNodes() */
#ifndef Q_OS_MAC
if (fs() && (source_left.column() == 0 || source_left.column() == 1)) {
bool leftD = fs()->isDir(source_left);
bool rightD = fs()->isDir(source_right);
if (leftD ^ rightD)
return leftD;
}
#endif
return QSortFilterProxyModel::lessThan(source_left, source_right);
}
-QFileSystemModel* StoreModel::fs() const {
- return static_cast<QFileSystemModel*>(sourceModel());
+QFileSystemModel *StoreModel::fs() const
+{
+ return static_cast<QFileSystemModel *>(sourceModel());
}
diff --git a/src/usersdialog.cpp b/src/usersdialog.cpp
index f3ba519..c649344 100644
--- a/src/usersdialog.cpp
+++ b/src/usersdialog.cpp
@@ -1,182 +1,181 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "usersdialog.h"
#include "pass.h"
#include "ui_usersdialog.h"
#include "ui_userswidget.h"
+#include <KLocalizedString>
#include <QMessageBox>
#include <QRegularExpression>
#include <QWidget>
#include <utility>
-#include <KLocalizedString>
void UsersWidgetData::generateUserList()
{
QList<UserInfo> users = m_pass.listKeys();
QList<UserInfo> selected_users;
int count = 0;
QStringList recipients = m_pass.getRecipientList(m_dir);
if (!recipients.isEmpty())
selected_users = m_pass.listKeys(recipients);
for (const UserInfo &sel : std::as_const(selected_users)) {
for (auto &user : users)
if (sel.key_id == user.key_id)
user.enabled = true;
}
if (count > selected_users.size()) {
// Some keys seem missing from keyring, add them separately
const QStringList missingRecipients = m_pass.getRecipientList(m_dir.isEmpty() ? QString{} : m_dir);
for (const QString &missingRecipient : std::as_const(recipients)) {
if (m_pass.listKeys(missingRecipient).empty()) {
UserInfo i;
i.enabled = true;
i.key_id = missingRecipient;
i.name = QStringLiteral(" ?? ") + i18n("Key not found in keyring");
users.append(i);
}
}
}
m_userList = users;
}
void UsersWidgetData::init()
{
m_pass.Init(m_dir, m_userList);
}
void UsersWidgetData::setUi(Ui_UsersWidget *u)
{
this->ui = u;
QObject::connect(ui->listWidget, &QListWidget::itemChanged, ui->listWidget, [this](auto item) {
itemChange(item);
});
QObject::connect(ui->checkBox, &QCheckBox::stateChanged, ui->checkBox, [this]() {
populateList();
});
QObject::connect(ui->lineEdit, &QLineEdit::textChanged, ui->lineEdit, [this]() {
populateList();
});
}
/**
* @brief UsersDialog::UsersDialog basic constructor
* @param parent
*/
-UsersDialog::UsersDialog(const QString& dir, Pass &pass, QWidget *parent)
+UsersDialog::UsersDialog(const QString &dir, Pass &pass, QWidget *parent)
: QDialog(parent)
, dialogUi(std::make_unique<Ui_UsersDialog>())
, ui(std::make_unique<Ui_UsersWidget>())
, d(pass)
{
d.m_dir = dir;
dialogUi->setupUi(this);
ui->setupUi(dialogUi->widget);
d.generateUserList();
d.setUi(ui.get());
if (d.m_userList.isEmpty()) {
QMessageBox::critical(parent, i18n("Keylist missing"), i18n("Could not fetch list of available GPG keys"));
return;
}
d.populateList();
connect(dialogUi->buttonBox, &QDialogButtonBox::accepted, this, &UsersDialog::accept);
connect(dialogUi->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
ui->lineEdit->setClearButtonEnabled(true);
}
/**
* @brief UsersDialog::~UsersDialog basic destructor.
*/
UsersDialog::~UsersDialog() = default;
Q_DECLARE_METATYPE(UserInfo *)
/**
* @brief UsersDialog::accept
*/
void UsersDialog::accept()
{
d.init();
QDialog::accept();
}
/**
* @brief UsersDialog::itemChange update the item information.
* @param item
*/
void UsersWidgetData::itemChange(QListWidgetItem *item)
{
if (!item)
return;
auto *info = item->data(Qt::UserRole).value<UserInfo *>();
if (!info)
return;
info->enabled = item->checkState() == Qt::Checked;
}
/**
* @brief UsersDialog::populateList update the view based on filter options
* (such as searching).
* @param filter
*/
void UsersWidgetData::populateList()
{
const auto filter = ui->lineEdit->text();
QRegularExpression nameFilter(QRegularExpression::wildcardToRegularExpression(QStringLiteral("*") + filter + QStringLiteral("*")),
QRegularExpression::CaseInsensitiveOption);
ui->listWidget->clear();
if (!m_userList.isEmpty()) {
for (auto &user : m_userList) {
if (filter.isEmpty() || nameFilter.match(user.name).hasMatch()) {
if (!user.isValid() && !ui->checkBox->isChecked())
continue;
if (user.expiry.toSecsSinceEpoch() > 0 && user.expiry.daysTo(QDateTime::currentDateTime()) > 0 && !ui->checkBox->isChecked())
continue;
QString userText = user.name + QStringLiteral("\n") + user.key_id;
if (user.created.toSecsSinceEpoch() > 0) {
userText +=
- QStringLiteral(" ") + i18nc("time of key creation","created %1", QLocale::system().toString(user.created, QLocale::ShortFormat));
+ QStringLiteral(" ") + i18nc("time of key creation", "created %1", QLocale::system().toString(user.created, QLocale::ShortFormat));
}
if (user.expiry.toSecsSinceEpoch() > 0)
- userText +=
- QStringLiteral(" ") + i18nc("Time of key expiry","expires %1", QLocale::system().toString(user.expiry, QLocale::ShortFormat));
+ userText += QStringLiteral(" ") + i18nc("Time of key expiry", "expires %1", QLocale::system().toString(user.expiry, QLocale::ShortFormat));
auto *item = new QListWidgetItem(userText, ui->listWidget);
item->setCheckState(user.enabled ? Qt::Checked : Qt::Unchecked);
item->setData(Qt::UserRole, QVariant::fromValue(&user));
if (user.have_secret) {
// item->setForeground(QColor(32, 74, 135));
item->setForeground(Qt::blue);
QFont font;
font.setFamily(font.defaultFamily());
font.setBold(true);
item->setFont(font);
} else if (!user.isValid()) {
item->setBackground(QColor(164, 0, 0));
item->setForeground(Qt::white);
} else if (user.expiry.toSecsSinceEpoch() > 0 && user.expiry.daysTo(QDateTime::currentDateTime()) > 0) {
item->setForeground(QColor(164, 0, 0));
} else if (!user.fullyValid()) {
item->setBackground(QColor(164, 80, 0));
item->setForeground(Qt::white);
}
ui->listWidget->addItem(item);
}
}
}
}
diff --git a/src/usersdialog.h b/src/usersdialog.h
index 4a2ddbf..0244eaf 100644
--- a/src/usersdialog.h
+++ b/src/usersdialog.h
@@ -1,65 +1,66 @@
/*
SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
SPDX-FileCopyrightText: 2023 g10 Code GmbH
SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>
SPDX-License-Identifier: GPL-3.0-or-later
*/
#ifndef USERSDIALOG_H_
#define USERSDIALOG_H_
#include "userinfo.h"
#include <QDialog>
#include <QList>
class Ui_UsersDialog;
class Ui_UsersWidget;
class Pass;
class QCloseEvent;
class QKeyEvent;
class QListWidgetItem;
struct UsersWidgetData {
explicit UsersWidgetData(Pass &pass)
- : m_pass(pass), ui(nullptr)
+ : m_pass(pass)
+ , ui(nullptr)
{
}
QList<UserInfo> m_userList;
QString m_dir;
Pass &m_pass;
Ui_UsersWidget *ui;
void generateUserList();
void init();
void setUi(Ui_UsersWidget *ui);
void itemChange(QListWidgetItem *item);
void populateList();
};
/*!
\class UsersDialog
\brief Handles listing and editing of GPG users.
Selection of whom to encrypt to.
*/
class UsersDialog : public QDialog
{
Q_OBJECT
public:
- explicit UsersDialog(const QString& dir, Pass &pass, QWidget *parent = nullptr);
+ explicit UsersDialog(const QString &dir, Pass &pass, QWidget *parent = nullptr);
~UsersDialog();
public Q_SLOTS:
void accept() override;
private:
std::unique_ptr<Ui_UsersDialog> dialogUi;
std::unique_ptr<Ui_UsersWidget> ui;
UsersWidgetData d;
};
#endif // USERSDIALOG_H_

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 6, 3:13 PM (1 h, 41 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
bf/23/ef16d910f73599175cc026b822ce

Event Timeline