Page MenuHome GnuPG

No OneTemporary

diff --git a/.kde-ci.yml b/.kde-ci.yml
index d46dd2f..8ad6469 100644
--- a/.kde-ci.yml
+++ b/.kde-ci.yml
@@ -1,17 +1,19 @@
# SPDX-FileCopyrightText: None
# SPDX-License-Identifier: CC0-1.0
Dependencies:
- 'on': ['@all']
'require':
'frameworks/extra-cmake-modules': '@stable'
'frameworks/ki18n' : '@stable'
'frameworks/kconfigwidgets' : '@stable'
'frameworks/kiconthemes' : '@stable'
'frameworks/prison' : '@stable'
Options:
test-before-installing: True
require-passing-tests-on: [ 'Linux', 'Windows' ]
cppcheck-ignore-files:
- tests
+ cppcheck-arguments:
+ - --suppress=useStlAlgorithm
diff --git a/src/clipboardhelper.h b/src/clipboardhelper.h
index 3390260..b5f8641 100644
--- a/src/clipboardhelper.h
+++ b/src/clipboardhelper.h
@@ -1,45 +1,45 @@
/*
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 CLIPBOARDHELPER_H
#define CLIPBOARDHELPER_H
#include <QObject>
#include <QTimer>
class QMainWindow;
/**
* Helper for Clipboard interactions
*
* This should be the only file where clipboard interactions
* should happen. This ensures we can attempt to clear it after some
* interval to try help avoid leaking passwords thru the clipboard
*/
class ClipboardHelper : public QObject
{
Q_OBJECT
public:
- ClipboardHelper(QMainWindow *mainWindow);
+ explicit ClipboardHelper(QMainWindow *mainWindow);
~ClipboardHelper();
void setClippedText(const QString &);
void clearClippedText();
void setClipboardTimer();
private:
QMainWindow *m_mainWindow;
QTimer clearClipboardTimer;
QString clippedText;
public Q_SLOTS:
void clearClipboard();
void copyTextToClipboard(const QString &text);
};
#endif // CLIPBOARDHELPER_H
diff --git a/src/configdialog.cpp b/src/configdialog.cpp
index f053c7b..89777b0 100644
--- a/src/configdialog.cpp
+++ b/src/configdialog.cpp
@@ -1,311 +1,302 @@
/*
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 <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>
#include <QTableWidgetItem>
#include <gpgpass_version.h>
#include <KI18n/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->storePath->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());
useTemplate(Settings::isUseTemplate());
ui->profileTable->verticalHeader()->hide();
ui->profileTable->horizontalHeader()->setStretchLastSection(true);
ui->label->setText(ui->label->text() + QString::fromUtf8(GPGPASS_VERSION_STRING));
connect(ui->profileTable, &QTableWidget::itemChanged, this, &ConfigDialog::onProfileTableItemChanged);
connect(this, &ConfigDialog::accepted, this, &ConfigDialog::on_accepted);
connect(ui->toolButtonStore, &QAbstractButton::clicked, this, &ConfigDialog::selectStoreFolder);
connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &ConfigDialog::toggleTemplateSubentries);
connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &ConfigDialog::toggleAutoClearPanelSubentries);
connect(ui->addButton, &QAbstractButton::clicked, this, &ConfigDialog::addProfile);
connect(ui->deleteButton, &QAbstractButton::clicked, this, &ConfigDialog::deleteSelectedProfile);
}
ConfigDialog::~ConfigDialog() = default;
void ConfigDialog::validate(QTableWidgetItem *item)
{
bool status = true;
if (item == nullptr) {
for (int i = 0; i < ui->profileTable->rowCount(); i++) {
for (int j = 0; j < ui->profileTable->columnCount(); j++) {
QTableWidgetItem *_item = ui->profileTable->item(i, j);
if (_item->text().isEmpty()) {
_item->setBackground(Qt::red);
status = false;
break;
}
}
if (!status)
break;
}
} else {
if (item->text().isEmpty()) {
item->setBackground(Qt::red);
status = false;
}
}
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status);
}
void ConfigDialog::on_accepted()
{
Settings::setPassStore(Util::normalizeFolderPath(ui->storePath->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()) // TODO(annejan) call check
ui->storePath->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::useAutoclear set the clipboard autoclear use from
* MainWindow.
* @param useAutoclear
*/
void ConfigDialog::useAutoclear(bool useAutoclear)
{
ui->checkBoxAutoclear->setChecked(useAutoclear);
}
/**
* @brief ConfigDialog::useAutoclearPanel set the panel autoclear use from
* MainWindow.
* @param useAutoclearPanel
*/
void ConfigDialog::useAutoclearPanel(bool useAutoclearPanel)
{
ui->checkBoxAutoclearPanel->setChecked(useAutoclearPanel);
toggleAutoClearPanelSubentries(useAutoclearPanel);
}
/**
* @brief ConfigDialog::setProfiles set the profiles and chosen profile from
* MainWindow.
* @param profiles
* @param profile
*/
-void ConfigDialog::setProfiles(QHash<QString, QString> profiles, QString profile)
+void ConfigDialog::setProfiles(const QHash<QString, QString>& profiles, const QString& profile)
{
- if (profiles.contains(QString{})) {
- profiles.remove({});
- // remove weird "" key value pairs
- }
-
- ui->profileTable->setRowCount(profiles.count());
+ ui->profileTable->clear();
+ ui->profileTable->setSortingEnabled(false);
QHashIterator<QString, QString> i(profiles);
- int n = 0;
while (i.hasNext()) {
i.next();
if (!i.value().isEmpty() && !i.key().isEmpty()) {
- ui->profileTable->setItem(n, 0, new QTableWidgetItem(i.key()));
- ui->profileTable->setItem(n, 1, new QTableWidgetItem(i.value()));
- if (i.key() == profile)
- ui->profileTable->selectRow(n);
+ ui->profileTable->insertRow(0);
+ ui->profileTable->setItem(0, 0, new QTableWidgetItem(i.key()));
+ ui->profileTable->setItem(0, 1, new QTableWidgetItem(i.value()));
}
- ++n;
}
+ 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()
{
+ ui->profileTable->setSortingEnabled(false);
int n = ui->profileTable->rowCount();
ui->profileTable->insertRow(n);
ui->profileTable->setItem(n, 0, new QTableWidgetItem());
ui->profileTable->setItem(n, 1, new QTableWidgetItem(ui->storePath->text()));
ui->profileTable->selectRow(n);
ui->deleteButton->setEnabled(true);
+ ui->profileTable->setSortingEnabled(true);
validate();
}
/**
* @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 : qAsConst(rows))
ui->profileTable->removeRow(row);
if (ui->profileTable->rowCount() < 1)
ui->deleteButton->setEnabled(false);
+ ui->profileTable->setSortingEnabled(true);
validate();
}
-/**
- * @brief ConfigDialog::criticalMessage weapper for showing critical messages
- * in a popup.
- * @param title
- * @param text
- */
-void ConfigDialog::criticalMessage(const QString &title, const QString &text)
-{
- QMessageBox::critical(this, title, text, QMessageBox::Ok, QMessageBox::Ok);
-}
-
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);
}
void ConfigDialog::onProfileTableItemChanged(QTableWidgetItem *item)
{
validate(item);
}
/**
* @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 9d4fb64..c85d18c 100644
--- a/src/configdialog.h
+++ b/src/configdialog.h
@@ -1,71 +1,68 @@
/*
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();
void useAutoclear(bool useAutoclear);
void useAutoclearPanel(bool useAutoclearPanel);
QHash<QString, QString> getProfiles();
void setPasswordConfiguration(const PasswordConfiguration &config);
PasswordConfiguration getPasswordConfiguration();
void useTemplate(bool useTemplate);
private Q_SLOTS:
void on_accepted();
void selectStoreFolder();
void addProfile();
void deleteSelectedProfile();
void toggleAutoClearPanelSubentries(bool enable);
void toggleTemplateSubentries(bool enable);
void onProfileTableItemChanged(QTableWidgetItem *item);
private:
QScopedPointer<Ui::ConfigDialog> ui;
- void setProfiles(QHash<QString, QString>, QString);
+ void setProfiles(const QHash<QString, QString>&, const QString&);
QString selectFolder();
- // QMessageBox::critical with hack to avoid crashes with
- // Qt 5.4.1 when QApplication::exec was not yet called
- void criticalMessage(const QString &title, const QString &text);
void validate(QTableWidgetItem *item = nullptr);
MainWindow *mainWindow;
};
#endif // CONFIGDIALOG_H_
diff --git a/src/firsttimedialog.cpp b/src/firsttimedialog.cpp
index ac06acc..e337ea5 100644
--- a/src/firsttimedialog.cpp
+++ b/src/firsttimedialog.cpp
@@ -1,296 +1,296 @@
/*
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 <QFileDialog>
#include <QHBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QRegularExpressionValidator>
#include "gpgmehelpers.h"
#include "settings.h"
#include "ui_keygenwidget.h"
#include "ui_selectpasswordstore.h"
#include "ui_userswidget.h"
#include "util.h"
#include <QGpgME/KeyListJob>
#include <QGpgME/QuickJob>
#include <gpgme++/keylistresult.h>
#include <KI18n/KLocalizedString>
DialogState::DialogState(Pass &p)
: pass(p)
{
}
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::fromTime_t(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::fromTime_t(key.subkey(0).expirationTime());
ui.have_secret = key.hasSecret();
users.append(ui);
}
return users;
}
FirstTimeDialog::FirstTimeDialog(QWidget *mainWindow, Pass &pass)
: m_mainWindow(mainWindow)
, m_state(pass)
{
setWindowTitle(i18n("GnuPG Password Manager setup"));
setPage(Intro, new IntroPage(m_state));
setPage(KeyGen, new KeyGenPage(m_state));
setPage(PasswordStore, new PasswordStorePage(m_state));
setPage(KeySelect, new KeySelectPage(m_state));
setPage(Done, new DonePage());
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::done(int i)
{
if (i == QDialog::DialogCode::Accepted) {
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"));
m_mainWindow->show();
}
QWizard::done(i);
}
int FirstTimeDialog::nextId() const
{
switch (currentId()) {
case Intro:
if (m_state.privateKeys().isEmpty()) {
return KeyGen;
} else {
return PasswordStore;
}
case KeyGen:
return PasswordStore;
case PasswordStore:
if (QFile::exists(m_state.storePath + QStringLiteral("/.gpg-id"))) {
return Done;
} else {
return KeySelect;
}
case KeySelect:
return Done;
default:
return -1;
}
return -1;
};
IntroPage::IntroPage(DialogState &s)
: m_state(s)
{
QVBoxLayout *lay = new QVBoxLayout();
lay->addWidget(new QLabel(i18n("Welcome to GnuPG Password manager")));
setTitle(i18n("Welcome"));
setSubTitle(i18n("Setting up"));
setLayout(lay);
}
KeyGenPage::KeyGenPage(DialogState &s)
: m_state(s)
{
setTitle(i18n("Generate keys"));
setSubTitle(i18n("Generate keys"));
m_ui = std::make_unique<Ui_KeyGenWidget>();
m_ui->setupUi(this);
m_ui->spinner->hide();
m_ui->generateButton->setEnabled(false);
m_ui->message->hide();
m_ui->email->setValidator(
new QRegularExpressionValidator(QRegularExpression(QRegularExpression::anchoredPattern(QStringLiteral(R"(\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b)")),
QRegularExpression::CaseInsensitiveOption)));
connect(m_ui->generateButton, &QPushButton::clicked, this, &KeyGenPage::startKeyGen);
connect(m_ui->email, &QLineEdit::textChanged, this, &KeyGenPage::checkEntries);
connect(m_ui->name, &QLineEdit::textChanged, this, &KeyGenPage::checkEntries);
}
void KeyGenPage::startKeyGen()
{
m_ui->email->setEnabled(false);
m_ui->name->setEnabled(false);
m_ui->generateButton->setEnabled(false);
m_ui->includeComment->setEnabled(false);
m_ui->spinner->show();
auto job = m_state.protocol->quickJob();
connect(job, &QGpgME::QuickJob::result, this, [this](auto &&result, const QString &log, auto &&logResult) {
if (isSuccess(result)) {
Q_EMIT this->completeChanged();
this->m_ui->spinner->hide();
this->m_ui->message->show();
this->m_ui->message->setText(i18n("Key generated"));
} else {
this->m_ui->message->setText(fromGpgmeCharStar(result.asString()));
this->m_ui->message->show();
}
Q_UNUSED(log);
Q_UNUSED(logResult);
});
QString uid;
if (m_ui->includeComment->isChecked()) {
uid = QStringLiteral("%1 (PasswordManager) <%2>");
} else {
uid = QStringLiteral("%1 <%2>");
}
job->startCreate(uid.arg(m_ui->name->text(), m_ui->email->text()), "future-default");
}
void KeyGenPage::checkEntries()
{
auto emailValidator = m_ui->email->validator();
bool enable = false;
if (emailValidator) {
auto email = m_ui->email->text();
int i = 0;
if (emailValidator->validate(email, i) == QValidator::Acceptable) {
qDebug() << "email valid";
enable = m_ui->name->text().size() > 4;
} else {
qDebug() << "email invalid";
}
} else {
qDebug() << "no email validator";
// TODO_REMOV
enable = true;
}
m_ui->generateButton->setEnabled(enable);
}
KeyGenPage::~KeyGenPage() = default;
bool KeyGenPage::isComplete() const
{
return !m_state.privateKeys().isEmpty();
}
PasswordStorePage::PasswordStorePage(DialogState &s)
: m_ui(std::make_unique<Ui_SelectPasswordStore>())
, m_state(s)
{
setTitle(i18n("Select password store"));
setSubTitle(i18n("Folder for selection"));
m_ui->setupUi(this);
m_ui->lineEdit->setText(Util::findPasswordStore());
connect(m_ui->pushButton, &QPushButton::clicked, this, [this]() {
QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::Directory);
dialog.setFilter(QDir::NoFilter);
dialog.setOption(QFileDialog::ShowDirsOnly);
QString result;
if (dialog.exec())
result = dialog.selectedFiles().constFirst();
m_ui->lineEdit->setText(result);
});
m_ui->label->hide();
connect(m_ui->lineEdit, &QLineEdit::textChanged, this, &PasswordStorePage::completeChanged);
connect(m_ui->checkBox, &QCheckBox::stateChanged, this, &PasswordStorePage::completeChanged);
}
bool PasswordStorePage::isComplete() const
{
QString dir = m_ui->lineEdit->text();
if (QDir(dir).exists()) {
m_ui->label->hide();
return true;
}
if (m_ui->checkBox->isChecked()) {
m_ui->label->hide();
return true;
}
m_ui->label->show();
return false;
}
bool PasswordStorePage::validatePage()
{
m_state.storePath = Util::normalizeFolderPath(QDir::fromNativeSeparators(m_ui->lineEdit->text()));
Settings::setPassStore(m_state.storePath);
return QDir().mkpath(m_state.storePath);
}
PasswordStorePage::~PasswordStorePage() = default;
KeySelectPage::KeySelectPage(DialogState &s)
: m_ui(std::make_unique<Ui_UsersWidget>())
, m_state(s)
, d(m_state.pass)
{
setTitle(i18n("KeySelectPage"));
setSubTitle(i18n("Describe users with access"));
m_ui->setupUi(this);
d.setUi(m_ui.get());
connect(m_ui->listWidget, &QListWidget::itemChanged, this, &KeySelectPage::completeChanged);
}
void KeySelectPage::initializePage()
{
d.m_dir = m_state.storePath;
d.generateUserList();
d.populateList();
}
bool KeySelectPage::isComplete() const
{
- for (auto &item : std::as_const(d.m_userList)) {
+ for (const auto &item : std::as_const(d.m_userList)) {
if (item.enabled) {
return true;
}
}
return false;
}
bool KeySelectPage::validatePage()
{
d.init();
return true;
}
KeySelectPage::~KeySelectPage() = default;
DonePage::DonePage()
{
setTitle(i18n("Setup done"));
setSubTitle(i18n("Thanks"));
setFinalPage(true);
}
diff --git a/src/firsttimedialog.h b/src/firsttimedialog.h
index 3efdd64..759046a 100644
--- a/src/firsttimedialog.h
+++ b/src/firsttimedialog.h
@@ -1,107 +1,107 @@
/*
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 Ui_KeyGenWidget;
class Ui_UsersWidget;
class Ui_KeyGenWidget;
class Ui_SelectPasswordStore;
class DialogState : public QObject
{
Q_OBJECT
public:
- DialogState(Pass &p);
+ explicit DialogState(Pass &p);
QList<UserInfo> privateKeys() const;
QGpgME::Protocol *protocol = QGpgME::openpgp();
QString storePath;
Pass &pass;
};
class FirstTimeDialog : public QWizard
{
enum Pages { Intro, KeyGen, PasswordStore, KeySelect, Done };
Q_OBJECT
public:
FirstTimeDialog(QWidget *mainWindow, Pass &pass);
int nextId() const override;
void done(int i) override;
private:
QWidget *m_mainWindow;
DialogState m_state;
};
class IntroPage : public QWizardPage
{
Q_OBJECT
public:
- IntroPage(DialogState &s);
+ explicit IntroPage(DialogState &s);
private:
DialogState &m_state;
};
class KeyGenPage : public QWizardPage
{
Q_OBJECT
public:
- KeyGenPage(DialogState &s);
+ explicit KeyGenPage(DialogState &s);
bool isComplete() const override;
~KeyGenPage();
private Q_SLOTS:
void startKeyGen();
void checkEntries();
private:
DialogState &m_state;
std::unique_ptr<Ui_KeyGenWidget> m_ui;
};
class PasswordStorePage : public QWizardPage
{
Q_OBJECT
public:
- PasswordStorePage(DialogState &s);
+ explicit PasswordStorePage(DialogState &s);
bool validatePage() override;
~PasswordStorePage();
bool isComplete() const override;
private:
std::unique_ptr<Ui_SelectPasswordStore> m_ui;
DialogState &m_state;
};
class KeySelectPage : public QWizardPage
{
Q_OBJECT
public:
- KeySelectPage(DialogState &s);
+ explicit KeySelectPage(DialogState &s);
bool isComplete() const override;
bool validatePage() override;
void initializePage() override;
~KeySelectPage();
private:
std::unique_ptr<Ui_UsersWidget> m_ui;
DialogState &m_state;
UsersWidgetData d;
};
class DonePage : public QWizardPage
{
public:
DonePage();
};
#endif // FIRSTTIMEDIALOG_H
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index f4a614e..2a96302 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,961 +1,959 @@
/*
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/prison.h>
#include <KI18n/KLocalizedString>
#include <QCloseEvent>
#include <QDialog>
#include <QFileInfo>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#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);
connect(m_pass.get(), &Pass::errorString, this, [this](auto str) {
flashText(str, true);
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);
}
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 = true;
// 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);
*/
QString passStore = Settings::getPassStore(Util::findPasswordStore());
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);
if (Settings::isNoLineWrapping()) {
ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
}
ui->textBrowser->setOpenExternalLinks(true);
ui->textBrowser->setContextMenuPolicy(Qt::DefaultContextMenu);
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()));
}
QMainWindow::setVisible(visible);
}
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(ui->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("applications-system")));
}
/**
* @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)));
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();
}
void MainWindow::flashText(const QString &text, const bool isError, const bool isHtml)
{
if (isError)
ui->textBrowser->setTextColor(Qt::red);
if (isHtml) {
QString _text = text;
if (!ui->textBrowser->toPlainText().isEmpty())
_text = ui->textBrowser->toHtml() + _text;
ui->textBrowser->setHtml(_text);
} else {
ui->textBrowser->setText(text);
ui->textBrowser->setTextColor(Qt::black);
}
}
/**
* @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) {
// Update the textBrowser line wrap mode
if (Settings::isNoLineWrapping()) {
ui->textBrowser->setLineWrapMode(QTextBrowser::NoWrap);
} else {
ui->textBrowser->setLineWrapMode(QTextBrowser::WidgetWidth);
}
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());
if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
m_pass->Show(file);
} else {
clearPanel(false);
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(true);
}
}
/**
* @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{});
clearPanel(false);
}
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:
clearTemplateWidgets();
// 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(0, i18n("Password"), password);
}
NamedValues namedValues = fileContent.getNamedValues();
for (int j = 0; j < namedValues.length(); ++j) {
NamedValue nv = namedValues.at(j);
addToGridLayout(j + 1, nv.name, nv.value);
}
if (ui->gridLayout->count() == 0)
ui->verticalLayoutPassword->setSpacing(0);
else
ui->verticalLayoutPassword->setSpacing(6);
output = fileContent.getRemainingDataForDisplay();
}
if (Settings::isUseAutoclearPanel()) {
clearPanelTimer.start();
}
output = output.toHtmlEscaped();
output.replace(Util::protocolRegex(), QStringLiteral(R"(<a href="\1">\1</a>)"));
output.replace(QStringLiteral("\n"), QStringLiteral("<br />"));
flashText(output, false, true);
setUiElementsEnabled(true);
}
/**
* @brief MainWindow::clearPanel hide the information from shoulder surfers
*/
void MainWindow::clearPanel(bool notify)
{
while (ui->gridLayout->count() > 0) {
QLayoutItem *item = ui->gridLayout->takeAt(0);
delete item->widget();
delete item;
}
if (notify) {
QString output = i18n("*** Password and Content hidden ***");
ui->textBrowser->setHtml(output);
} else {
ui->textBrowser->setHtml(QString{});
}
}
/**
* @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(false);
ui->passwordName->setText(QString{});
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);
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);
QDirIterator it(model.rootPath() + QDir::separator() + file, QDirIterator::Subdirectories);
- bool okDir = true;
- while (it.hasNext() && okDir) {
+ while (it.hasNext()) {
it.next();
if (auto fi = it.fileInfo(); fi.isFile()) {
if (fi.suffix() != QStringLiteral("gpg")) {
- okDir = false;
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);
}
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)
{
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
UsersDialog d(dir, *m_pass, this);
if (!d.exec()) {
ui->treeView->setFocus();
}
}
/**
* @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();
}
}
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()) {
ui->profileWidget->hide();
} else {
ui->profileWidget->show();
ui->profileBox->setEnabled(profiles.size() > 1);
ui->profileBox->clear();
QHashIterator<QString, QString> i(profiles);
while (i.hasNext()) {
i.next();
if (!i.key().isEmpty())
ui->profileBox->addItem(i.key());
}
ui->profileBox->model()->sort(0);
}
int index = ui->profileBox->findText(Settings::getProfile());
if (index != -1) // -1 for not found
ui->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();
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())));
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
}
/**
* @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()) {
- QAction *addFolder = contextMenu.addAction(i18n("Add folder"));
- QAction *addPassword = contextMenu.addAction(i18n("Add password"));
- QAction *users = contextMenu.addAction(i18n("Users"));
- connect(addFolder, &QAction::triggered, this, &MainWindow::addFolder);
- connect(addPassword, &QAction::triggered, this, &MainWindow::addPassword);
- connect(users, &QAction::triggered, this, &MainWindow::onUsers);
+ 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()) {
- QAction *edit = contextMenu.addAction(i18n("Edit"));
+ const QAction *edit = contextMenu.addAction(i18n("Edit"));
connect(edit, &QAction::triggered, this, &MainWindow::onEdit);
}
if (selected) {
contextMenu.addSeparator();
if (fileOrFolder.isDir()) {
- QAction *renameFolder = contextMenu.addAction(i18n("Rename folder"));
- connect(renameFolder, &QAction::triggered, this, &MainWindow::renameFolder);
+ const QAction *renameFolderAction = contextMenu.addAction(i18n("Rename folder"));
+ connect(renameFolderAction, &QAction::triggered, this, &MainWindow::renameFolder);
} else if (fileOrFolder.isFile()) {
- QAction *renamePassword = contextMenu.addAction(i18n("Rename password"));
- connect(renamePassword, &QAction::triggered, this, &MainWindow::renamePassword);
+ const QAction *renamePasswordAction = contextMenu.addAction(i18n("Rename password"));
+ connect(renamePasswordAction, &QAction::triggered, this, &MainWindow::renamePassword);
}
- QAction *deleteItem = contextMenu.addAction(i18n("Delete"));
+ 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->gridLayout->count() > 0) {
QLayoutItem *item = ui->gridLayout->takeAt(0);
delete item->widget();
delete item;
}
ui->verticalLayoutPassword->setSpacing(0);
}
/**
* @brief MainWindow::addToGridLayout add a field to the template grid
* @param position
* @param field
* @param value
*/
void MainWindow::addToGridLayout(int position, 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
QFrame *frame = new QFrame();
QLayout *ly = new QHBoxLayout();
ly->setContentsMargins(5, 2, 2, 2);
ly->setSpacing(0);
frame->setLayout(ly);
auto fieldLabel = createPushButton(QIcon::fromTheme(QStringLiteral("edit-copy")), m_clipboardHelper, [this, trimmedValue] {
m_clipboardHelper->copyTextToClipboard(trimmedValue);
});
frame->layout()->addWidget(fieldLabel.release());
auto qrButton = createPushButton(QIcon::fromTheme(QStringLiteral("view-barcode-qr")), m_clipboardHelper, [this, trimmedValue]() {
auto barcode = Prison::createBarcode(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();
});
frame->layout()->addWidget(qrButton.release());
if (trimmedField == i18n("Password")) {
auto *line = new QLineEdit();
line->setObjectName(trimmedField);
line->setText(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);
auto showButton = createPushButton(icon, line, [line]() {
if (line->echoMode() == QLineEdit::Password) {
line->setEchoMode(QLineEdit::Normal);
} else {
line->setEchoMode(QLineEdit::Password);
}
});
showButton->setCheckable(true);
showButton->setContentsMargins(0, 0, 0, 0);
frame->layout()->addWidget(showButton.release());
frame->layout()->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(0, 0, 0, 0);
frame->layout()->addWidget(line);
}
// set into the layout
ui->gridLayout->addWidget(new QLabel(trimmedField), position, 0);
ui->gridLayout->addWidget(frame, position, 1);
}
/**
* @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(QString title, QString msg)
{
QMessageBox::critical(this, title, msg);
}
diff --git a/src/pass.cpp b/src/pass.cpp
index 53efa14..13e9f24 100644
--- a/src/pass.cpp
+++ b/src/pass.cpp
@@ -1,415 +1,436 @@
/*
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 <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 <KI18n/KLocalizedString>
+// TODO remove
+#include <QMessageBox>
+
using namespace std;
// TODO remove
-static QString ensurePassStore(const QString &file, const char *location)
+Q_REQUIRED_RESULT static bool ensurePassStore(const QString &file, const char *location)
{
if (file.startsWith(Settings::getPassStore())) {
- return file;
+ return true;
}
- qWarning() << "file not in pass store called from" << location << file << Settings::getPassStore();
- return Settings::getPassStore() + QStringLiteral("/") + file;
+ 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
-static QString ensureSuffix(const QString &file, const char *location)
+Q_REQUIRED_RESULT static bool ensureSuffix(const QString &file, const char *location)
{
if (file.endsWith(QStringLiteral(".gpg"))) {
- return file;
+ return true;
}
qWarning() << "file without suffix called from " << location;
- return file + QStringLiteral(".gpg");
+ 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!"));
}
return {};
}
/**
* @brief Pass::listKeys list users
* @param keystrings
* @param secret list private keys
* @return QList<UserInfo> users
*/
-QList<UserInfo> Pass::listKeys(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::fromTime_t(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::fromTime_t(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(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(QString for_file)
+QStringList Pass::getRecipientList(const QString& for_file)
{
- QDir gpgIdPath(QFileInfo(ensurePassStore(for_file, Q_FUNC_INFO)).absoluteDir());
+ 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(QString file)
+void Pass::Show(const QString& file)
{
- file = ensureSuffix(ensurePassStore(file, Q_FUNC_INFO), Q_FUNC_INFO);
+ 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 processErrorExit(result.error().code(), QString::fromLocal8Bit(result.error().asString()));
}
// Q_EMIT log(auditLog);
Q_UNUSED(logError);
Q_UNUSED(auditLog);
});
job->start(data);
}
-void Pass::Insert(QString file, QString newValue)
+void Pass::Insert(const QString& file, const QString& newValue)
{
- file = ensureSuffix(ensurePassStore(file, Q_FUNC_INFO), Q_FUNC_INFO);
+ 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."));
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(QString file, bool isDir)
+void Pass::Remove(const QString& file, bool isDir)
{
- file = ensurePassStore(file, Q_FUNC_INFO);
+ if (!ensurePassStore(file, Q_FUNC_INFO)) {
+ return;
+ }
if (!isDir) {
- file = ensureSuffix(file, Q_FUNC_INFO);
+ if(!ensureSuffix(file, Q_FUNC_INFO)) {
+ return;
+ }
QFile(file).remove();
} else {
QDir dir(file);
dir.removeRecursively();
}
}
-void Pass::Init(QString path, const QList<UserInfo> &users)
+void Pass::Init(const QString& path, const QList<UserInfo> &users)
{
- QString gpgIdFile = ensurePassStore(path, Q_FUNC_INFO) + QStringLiteral(".gpg-id");
+ 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!"));
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(ensurePassStore(dir, Q_FUNC_INFO), QStringList() << QStringLiteral("*.gpg"), QDir::Files, QDirIterator::Subdirectories);
+ 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 : qAsConst(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 5882295..dc09f2c 100644
--- a/src/pass.h
+++ b/src/pass.h
@@ -1,66 +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();
- ~Pass()
- {
- }
- void Show(QString file);
- void Insert(QString file, QString value);
- void Remove(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(QString path, const QList<UserInfo> &users);
+ 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(QStringList keystrings, bool secret = false);
- QList<UserInfo> listKeys(QString keystring = QString{}, bool secret = false);
- static QStringList getRecipientList(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 statusMsg(QString, int);
- void critical(QString, QString);
+ void statusMsg(const QString&, int);
+ void critical(const QString&, const QString&);
void processErrorExit(int exitCode, const QString &err);
void finishedShow(const QString &plainTextPassword);
void finishedInsert();
};
#endif // PASS_H
diff --git a/src/passworddialog.cpp b/src/passworddialog.cpp
index 4a89a88..22e8b8c 100644
--- a/src/passworddialog.cpp
+++ b/src/passworddialog.cpp
@@ -1,241 +1,241 @@
/*
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 <QLabel>
#include <QLineEdit>
/**
* @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);
}
/**
* @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(" ") + m_file);
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::processErrorExit, 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->checkBoxShow, &QCheckBox::stateChanged, this, &PasswordDialog::showPasswordChanged);
}
/**
* @brief Pass{}{}wordDialog::~PasswordDialog basic destructor.
*/
PasswordDialog::~PasswordDialog() = default;
/**
* @brief PasswordDialog::on_checkBoxShow_stateChanged hide or show passwords.
* @param arg1
*/
void PasswordDialog::showPasswordChanged(int arg1)
{
if (arg1)
ui->lineEditPassword->setEchoMode(QLineEdit::Normal);
else
ui->lineEditPassword->setEchoMode(QLineEdit::Password);
}
/**
* @brief PasswordDialog::on_createPasswordButton_clicked generate a random
* passwords.
* @todo refactor when process is untangled from MainWindow class.
*/
void PasswordDialog::createPassword()
{
ui->widget->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->setText(newPass);
ui->widget->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(QString password)
+void PasswordDialog::setPassword(const QString& password)
{
FileContent fileContent = FileContent::parse(password, m_templating ? m_fields : QStringList(), m_allFields);
ui->lineEditPassword->setText(fileContent.getPassword());
QWidget *previous = ui->checkBoxShow;
// first set templated values
NamedValues namedValues = fileContent.getNamedValues();
for (QLineEdit *line : qAsConst(templateLines)) {
line->setText(namedValues.takeValue(line->objectName()));
previous = line;
}
// show remaining values (if there are)
otherLines.clear();
for (const NamedValue &nv : qAsConst(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->text() + 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(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->checkBoxShow;
for (const QString &field : qAsConst(m_fields)) {
if (field.isEmpty())
continue;
auto *line = new QLineEdit();
line->setObjectName(field);
ui->formLayout->addRow(new QLabel(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
}
diff --git a/src/passworddialog.h b/src/passworddialog.h
index 6769d0e..71b5c1f 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(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(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 showPasswordChanged(int arg1);
void createPassword();
void dialogAccepted();
void dialogCancelled();
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/storemodel.h b/src/storemodel.h
index 7fcaddd..180c66e 100644
--- a/src/storemodel.h
+++ b/src/storemodel.h
@@ -1,56 +1,56 @@
/*
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
*/
#ifndef STOREMODEL_H_
#define STOREMODEL_H_
#include <QSortFilterProxyModel>
/*!
\class StoreModel
\brief The QSortFilterProxyModel for handling filesystem searches.
*/
class QFileSystemModel;
class Pass;
class StoreModel : public QSortFilterProxyModel
{
Q_OBJECT
private:
QFileSystemModel *fs() const;
Pass &m_pass;
public:
- StoreModel(Pass &pass);
+ explicit StoreModel(Pass &pass);
QVariant data(const QModelIndex &index, int role) const override;
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
// QAbstractItemModel interface
public:
Qt::DropActions supportedDropActions() const override;
Qt::DropActions supportedDragActions() const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
};
/*!
\struct dragAndDropInfo
\brief holds values to share beetween drag and drop on the passwordstorage
view
*/
struct dragAndDropInfoPasswordStore {
- bool isDir;
- bool isFile;
+ bool isDir = false;
+ bool isFile = false;
QString path;
};
#endif // STOREMODEL_H_
diff --git a/src/usersdialog.cpp b/src/usersdialog.cpp
index 4abe4e6..a261b26 100644
--- a/src/usersdialog.cpp
+++ b/src/usersdialog.cpp
@@ -1,182 +1,182 @@
/*
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 <QMessageBox>
#include <QRegularExpression>
#include <QWidget>
#include <utility>
#include <KI18n/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 : qAsConst(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 recipients = m_pass.getRecipientList(m_dir.isEmpty() ? QString{} : m_dir);
- for (const QString &recipient : recipients) {
- if (m_pass.listKeys(recipient).empty()) {
+ const QStringList missingRecipients = m_pass.getRecipientList(m_dir.isEmpty() ? QString{} : m_dir);
+ for (const QString &missingRecipient : qAsConst(recipients)) {
+ if (m_pass.listKeys(missingRecipient).empty()) {
UserInfo i;
i.enabled = true;
- i.key_id = recipient;
+ 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(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));
}
if (user.expiry.toSecsSinceEpoch() > 0)
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 169a6a6..4a2ddbf 100644
--- a/src/usersdialog.h
+++ b/src/usersdialog.h
@@ -1,65 +1,65 @@
/*
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 {
- UsersWidgetData(Pass &pass)
- : m_pass(pass)
+ explicit UsersWidgetData(Pass &pass)
+ : m_pass(pass), ui(nullptr)
{
}
QList<UserInfo> m_userList;
QString m_dir;
- Ui_UsersWidget *ui;
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(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, Feb 26, 6:42 PM (8 h, 31 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
54/34/1167d7f333f857e882cf311dc4c5

Event Timeline