Page MenuHome GnuPG

No OneTemporary

diff --git a/autotests/tst_userslistmodel.cpp b/autotests/tst_userslistmodel.cpp
index 8a6d61b..37417a3 100644
--- a/autotests/tst_userslistmodel.cpp
+++ b/autotests/tst_userslistmodel.cpp
@@ -1,55 +1,54 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl.schwan@gnupg.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "models/userslistmodel.h"
-#include "settings.h"
#include "setupenv.h"
#include <QAbstractItemModelTester>
#include <QObject>
#include <QSignalSpy>
#include <QTest>
class UsersListModelTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testModel();
};
void UsersListModelTest::initTestCase()
{
Test::setupEnv();
}
void UsersListModelTest::testModel()
{
auto model = new UsersListModel({"14B79E26050467AA", "02325448204E452A"}, this);
auto tester = new QAbstractItemModelTester(model);
Q_UNUSED(tester);
QSignalSpy spy(model, &UsersListModel::finishedKeysFetching);
spy.wait();
QCOMPARE(model->rowCount({}), 6);
// valid key with secrets
QCOMPARE(model->data(model->index(0, 0), Qt::DisplayRole).toString(),
QStringLiteral("unittest key (no password) <test@kolab.org>\n8D9860C58F246DE6 created 11/13/09 5:33\u202FPM"));
QCOMPARE(model->data(model->index(0, 0), UsersListModel::NameRole).toString(), QStringLiteral("unittest key (no password) <test@kolab.org>"));
QCOMPARE(model->data(model->index(0, 0), Qt::CheckStateRole).value<Qt::CheckState>(), Qt::Unchecked);
QCOMPARE(model->data(model->index(0, 0), Qt::UserRole).value<UserInfo>().have_secret, true);
QCOMPARE(model->data(model->index(0, 0), Qt::ForegroundRole).value<QColor>(), Qt::blue);
QCOMPARE(model->data(model->index(0, 0), Qt::FontRole).value<QFont>().bold(), 1);
// no secret
QCOMPARE(model->data(model->index(2, 0), UsersListModel::NameRole).toString(), QStringLiteral("Leonardo Franchi (lfranchi) <lfranchi@kde.org>"));
QCOMPARE(model->data(model->index(2, 0), Qt::UserRole).value<UserInfo>().have_secret, false);
QCOMPARE(model->data(model->index(2, 0), Qt::ForegroundRole).value<QColor>(), Qt::white);
// key not found in keyring
QCOMPARE(model->index(5, 0).data(UsersListModel::NameRole).toString(), QStringLiteral(" ?? Key not found in keyring"));
}
QTEST_MAIN(UsersListModelTest)
#include "tst_userslistmodel.moc"
\ No newline at end of file
diff --git a/main/main.cpp b/main/main.cpp
index 3b8d78c..1a23261 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1,72 +1,62 @@
/*
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 "settings.h"
#include "widgets/mainwindow.h"
#include <QApplication>
-#include <QDir>
-#include <QSettings>
-#include <QTranslator>
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
#include <KColorSchemeManager>
#endif
#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{});
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
KColorSchemeManager m;
#endif
w.show();
return app.exec();
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4ebc965..06b171d 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,123 +1,121 @@
# let's put most of the "meat" in a static library this way, we can also unit
# test parts of it
add_library(gpgpass_internal STATIC)
target_sources(gpgpass_internal PRIVATE
../resources.qrc
clipboardhelper.cpp
clipboardhelper.h
gpgmehelpers.h
gpgmehelpers.h
passentry.cpp
passentry.h
passphrasegenerator.cpp
passphrasegenerator.h
passwordhealth.cpp
passwordhealth.h
passwordgenerator.cpp
passwordgenerator.h
rootfoldersmanager.cpp
rootfoldersmanager.h
- settings.cpp
- settings.h
userinfo.cpp
userinfo.h
util.cpp
util.h
job/directoryreencryptjob.cpp
job/directoryreencryptjob.h
job/fileencryptjob.cpp
job/fileencryptjob.h
job/filedecryptjob.cpp
job/filedecryptjob.h
job/filereencryptjob.cpp
job/filereencryptjob.h
conf/configuredialog.cpp
conf/configuredialog.h
conf/generalconfigurationpage.cpp
conf/generalconfigurationpage.h
conf/gpgpassconfigmodule.cpp
conf/gpgpassconfigmodule.h
conf/gpgpasspageconfigdialog.cpp
conf/gpgpasspageconfigdialog.h
conf/rootfoldersconfigurationpage.cpp
conf/rootfoldersconfigurationpage.h
conf/templateconfigurationpage.cpp
conf/templateconfigurationpage.h
models/addfileinfoproxy.cpp
models/addfileinfoproxy.h
models/storemodel.cpp
models/storemodel.h
models/userslistmodel.cpp
models/userslistmodel.h
models/rootfoldersmodel.cpp
models/rootfoldersmodel.h
widgets/deselectabletreeview.h
widgets/formtextinput.cpp
widgets/formtextinput.h
widgets/mainwindow.cpp
widgets/mainwindow.h
widgets/passwordeditorwidget.cpp
widgets/passwordeditorwidget.h
widgets/passwordgeneratorwidget.cpp
widgets/passwordgeneratorwidget.h
widgets/passwordviewerwidget.cpp
widgets/passwordviewerwidget.h
widgets/qpushbuttonfactory.h
widgets/setupwidget.cpp
widgets/setupwidget.h
widgets/usersdialog.cpp
widgets/usersdialog.h
widgets/welcomewidget.cpp
widgets/welcomewidget.h
zxcvbn/zxcvbn.c
zxcvbn/zxcvbn.h
)
kconfig_add_kcfg_files(gpgpass_internal GENERATE_MOC
config.kcfgc
rootfolderconfig.kcfgc
)
ki18n_wrap_ui(gpgpass_internal
conf/generalconfigurationpage.ui
conf/rootfoldersconfigurationpage.ui
conf/templateconfigurationpage.ui
widgets/passwordeditorwidget.ui
widgets/passwordgeneratorwidget.ui
widgets/welcomewidget.ui
widgets/mainwindow.ui
widgets/usersdialog.ui
widgets/userswidget.ui
)
target_link_libraries(gpgpass_internal
Qt::Widgets
KF${QT_MAJOR_VERSION}::CoreAddons
KF${QT_MAJOR_VERSION}::ConfigCore
KF${QT_MAJOR_VERSION}::ConfigGui
KF${QT_MAJOR_VERSION}::Prison
KF${QT_MAJOR_VERSION}::IconThemes
KF${QT_MAJOR_VERSION}::I18n
KF${QT_MAJOR_VERSION}::WidgetsAddons
KF${QT_MAJOR_VERSION}::ItemModels
KF${QT_MAJOR_VERSION}::ItemViews
KF${QT_MAJOR_VERSION}::KIOWidgets
KPim${QT_MAJOR_VERSION}::Libkleo
)
if (QT_MAJOR_VERSION STREQUAL "6")
target_link_libraries(gpgpass_internal QGpgmeQt6 LibGpgError::LibGpgError KF6::ColorScheme)
target_sources(gpgpass_internal PRIVATE
job/openpgpcertificatecreationjob.cpp
job/openpgpcertificatecreationjob.h
)
else()
target_link_libraries(gpgpass_internal QGpgme KF5::ConfigWidgets)
endif()
diff --git a/src/conf/generalconfigurationpage.cpp b/src/conf/generalconfigurationpage.cpp
index b6dcc91..9eccafc 100644
--- a/src/conf/generalconfigurationpage.cpp
+++ b/src/conf/generalconfigurationpage.cpp
@@ -1,96 +1,95 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "generalconfigurationpage.h"
#include "config.h"
-#include "settings.h"
#include "ui_generalconfigurationpage.h"
using namespace GpgPass::Config;
GeneralConfigurationPage::GeneralConfigurationPage(QWidget *parent)
: GpgPassConfigModule(parent)
, ui(new Ui::GeneralConfigurationPage)
{
ui->setupUi(this);
connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &GeneralConfigurationPage::setAutoClearPanelSubentries);
connect(ui->checkBoxAutoclear, &QAbstractButton::toggled, this, &GeneralConfigurationPage::setAutoClearSubentries);
// Changed
connect(ui->checkBoxAutoclear, &QAbstractButton::toggled, this, &GeneralConfigurationPage::changed);
connect(ui->checkBoxAutoclearPanel, &QAbstractButton::toggled, this, &GeneralConfigurationPage::changed);
connect(ui->checkBoxDisplayAsIs, &QAbstractButton::toggled, this, &GeneralConfigurationPage::changed);
connect(ui->checkBoxNoLineWrapping, &QAbstractButton::toggled, this, &GeneralConfigurationPage::changed);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
connect(ui->spinBoxAutoclearPanelSeconds, &QSpinBox::valueChanged, this, &GeneralConfigurationPage::changed);
connect(ui->spinBoxAutoclearPanelSeconds, &QSpinBox::valueChanged, this, &GeneralConfigurationPage::changed);
#else
connect(ui->spinBoxAutoclearPanelSeconds, QOverload<int>::of(&QSpinBox::valueChanged), this, &GeneralConfigurationPage::changed);
connect(ui->spinBoxAutoclearPanelSeconds, QOverload<int>::of(&QSpinBox::valueChanged), this, &GeneralConfigurationPage::changed);
#endif
}
void GeneralConfigurationPage::save()
{
auto config = ::Config::self();
config->setClipboardAutoClearEnabled(ui->checkBoxAutoclear->isChecked());
config->setClipboardAutoClearTime(ui->spinBoxAutoclearSeconds->value());
config->setViewerAutoClearEnabled(ui->checkBoxAutoclearPanel->isChecked());
config->setViewerAutoClearTime(ui->spinBoxAutoclearPanelSeconds->value());
config->setDisplayAsIs(ui->checkBoxDisplayAsIs->isChecked());
config->setNoLineWrapping(ui->checkBoxNoLineWrapping->isChecked());
config->save();
}
void GeneralConfigurationPage::load()
{
const auto config = ::Config::self();
ui->spinBoxAutoclearSeconds->setValue(config->clipboardAutoClearTime());
setAutoclear(config->clipboardAutoClearEnabled());
ui->spinBoxAutoclearPanelSeconds->setValue(config->viewerAutoClearTime());
setAutoclearPanel(config->viewerAutoClearEnabled());
ui->checkBoxDisplayAsIs->setChecked(config->displayAsIs());
ui->checkBoxNoLineWrapping->setChecked(config->noLineWrapping());
}
void GeneralConfigurationPage::defaults()
{
auto config = ::Config::self();
ui->spinBoxAutoclearSeconds->setValue(config->defaultClipboardAutoClearTimeValue());
setAutoclear(config->defaultClipboardAutoClearEnabledValue());
ui->spinBoxAutoclearPanelSeconds->setValue(config->defaultViewerAutoClearTimeValue());
setAutoclearPanel(config->defaultViewerAutoClearEnabledValue());
ui->checkBoxDisplayAsIs->setChecked(config->defaultDisplayAsIsValue());
ui->checkBoxNoLineWrapping->setChecked(config->defaultNoLineWrappingValue());
Q_EMIT changed();
}
void GeneralConfigurationPage::setAutoClearSubentries(bool autoclear)
{
ui->spinBoxAutoclearSeconds->setEnabled(autoclear);
}
void GeneralConfigurationPage::setAutoclear(bool autoclear)
{
ui->checkBoxAutoclear->setChecked(autoclear);
setAutoClearSubentries(autoclear);
}
void GeneralConfigurationPage::setAutoclearPanel(bool autoclear)
{
ui->checkBoxAutoclearPanel->setChecked(autoclear);
setAutoClearPanelSubentries(autoclear);
}
void GeneralConfigurationPage::setAutoClearPanelSubentries(bool autoclear)
{
ui->spinBoxAutoclearPanelSeconds->setEnabled(autoclear);
ui->labelPanelSeconds->setEnabled(autoclear);
}
diff --git a/src/conf/templateconfigurationpage.cpp b/src/conf/templateconfigurationpage.cpp
index 407909d..7bf44e4 100644
--- a/src/conf/templateconfigurationpage.cpp
+++ b/src/conf/templateconfigurationpage.cpp
@@ -1,53 +1,60 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "templateconfigurationpage.h"
-#include "settings.h"
+#include "config.h"
#include "ui_templateconfigurationpage.h"
using namespace GpgPass::Config;
TemplateConfigurationPage::TemplateConfigurationPage(QWidget *parent)
: GpgPassConfigModule(parent)
, ui(new Ui::TemplateConfigurationPage)
{
ui->setupUi(this);
connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &TemplateConfigurationPage::toggleTemplateSubentries);
// Changed
connect(ui->checkBoxUseTemplate, &QAbstractButton::toggled, this, &TemplateConfigurationPage::changed);
connect(ui->checkBoxTemplateAllFields, &QAbstractButton::toggled, this, &TemplateConfigurationPage::changed);
connect(ui->plainTextEditTemplate, &QPlainTextEdit::textChanged, this, &TemplateConfigurationPage::changed);
}
void TemplateConfigurationPage::save()
{
- Settings::setUseTemplate(ui->checkBoxUseTemplate->isChecked());
- Settings::setPassTemplate(ui->plainTextEditTemplate->toPlainText());
- Settings::setTemplateAllFields(ui->checkBoxTemplateAllFields->isChecked());
+ auto config = ::Config::self();
+ config->setTemplateEnabled(ui->checkBoxUseTemplate->isChecked());
+ config->setPassTemplate(ui->plainTextEditTemplate->toPlainText().split(u'\n', Qt::SkipEmptyParts));
+ config->setTemplateAllFields(ui->checkBoxTemplateAllFields->isChecked());
+ config->save();
}
void TemplateConfigurationPage::load()
{
- ui->plainTextEditTemplate->setPlainText(Settings::getPassTemplate());
- ui->checkBoxTemplateAllFields->setChecked(Settings::isTemplateAllFields());
- useTemplate(Settings::isUseTemplate());
+ const auto config = ::Config::self();
+ ui->plainTextEditTemplate->setPlainText(config->passTemplate().join(u'\n'));
+ ui->checkBoxTemplateAllFields->setChecked(config->templateAllFields());
+ useTemplate(config->templateEnabled());
}
void TemplateConfigurationPage::defaults()
{
+ const auto config = ::Config::self();
+ ui->plainTextEditTemplate->setPlainText(config->defaultPassTemplateValue().join(u'\n'));
+ ui->checkBoxTemplateAllFields->setChecked(config->defaultTemplateAllFieldsValue());
+ useTemplate(config->defaultTemplateEnabledValue());
}
void TemplateConfigurationPage::toggleTemplateSubentries(bool enable)
{
ui->plainTextEditTemplate->setEnabled(enable);
ui->checkBoxTemplateAllFields->setEnabled(enable);
}
void TemplateConfigurationPage::useTemplate(bool useTemplate)
{
ui->checkBoxUseTemplate->setChecked(useTemplate);
toggleTemplateSubentries(useTemplate);
}
diff --git a/src/config.kcfg b/src/config.kcfg
index 5422e0b..a9ac1e4 100644
--- a/src/config.kcfg
+++ b/src/config.kcfg
@@ -1,142 +1,156 @@
<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
<!--
SPDX-FileCopyrightText: 2024 g10 Code GmbH
SPDX-FileContributor: Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: GPL-2.0-or-later
-->
<kcfgfile name="gpgpassrc" />
<group name="Clipboard">
<entry name="ClipboardAutoClearEnabled" type="bool">
<label>Autoclear clipboard</label>
<default>false</default>
</entry>
<entry name="ClipboardAutoClearTime" type="int">
<label>Autoclear delay in seconds for the clipboard</label>
<default>10</default>
<min>5</min>
</entry>
</group>
+ <group name="Template">
+ <entry name="TemplateEnabled" type="bool">
+ <label>Template feature is enabled</label>
+ <default>true</default>
+ </entry>
+ <entry name="PassTemplate" type="StringList">
+ <label>Templates</label>
+ <default>login,URL</default>
+ </entry>
+ <entry name="TemplateAllFields" type="bool">
+ <label>Template all fields</label>
+ <default>false</default>
+ </entry>
+ </group>
<group name="PasswordViewer">
<entry name="ViewerAutoClearEnabled" type="bool">
<label>Autoclear the password viewer</label>
<default>false</default>
</entry>
<entry name="ViewerAutoClearTime" type="int">
<label>Autoclear delay in seconds for the password viewer</label>
<default>10</default>
<min>5</min>
</entry>
<entry name="DisplayAsIs" type="bool">
<label>Display the content as is</label>
<default>false</default>
</entry>
<entry name="NoLineWrapping" type="bool">
<label>No line wrapping</label>
<default>false</default>
</entry>
</group>
<group name="PasswordGenerator">
<entry name="PasswordLength" type="int">
<label>Password length</label>
<default>20</default>
<min>5</min>
</entry>
<entry name="LowerCase" type="bool">
<label>Lower case</label>
<default>true</default>
</entry>
<entry name="UpperCase" type="bool">
<label>Upper case</label>
<default>true</default>
</entry>
<entry name="Numbers" type="bool">
<label>Number</label>
<default>true</default>
</entry>
<entry name="AdditionalChars" type="String">
<label>Additional Characters</label>
<default></default>
</entry>
<entry name="ExcludedChars" type="String">
<label>Excluded Characters</label>
<default></default>
</entry>
<entry name="Logograms" type="bool">
<label>Logograms</label>
<default>true</default>
</entry>
<entry name="SpecialCharacters" type="bool">
<label>Special Characters</label>
<default>true</default>
</entry>
<entry name="GeneratorAdvanced" type="bool">
<label>Advanced password generator options</label>
<default>false</default>
</entry>
<entry name="Eascii" type="bool">
<label>EASCII</label>
<default>false</default>
</entry>
<entry name="Braces" type="bool">
<label>Braces</label>
<default>false</default>
</entry>
<entry name="Quotes" type="bool">
<label>Quotes</label>
<default>false</default>
</entry>
<entry name="Punctuation" type="bool">
<label>Punctuation</label>
<default>false</default>
</entry>
<entry name="Dashes" type="bool">
<label>Dashes</label>
<default>false</default>
</entry>
<entry name="Math" type="bool">
<label>Math</label>
<default>false</default>
</entry>
<entry name="ExcludeAlike" type="bool">
<label>Exclude look alike characters</label>
<default>true</default>
</entry>
<entry name="EnsureEvery" type="bool">
<label>Pick characters from every group</label>
<default>true</default>
</entry>
<entry name="WordCount" type="int">
<label>Passphrase word's count</label>
<default>7</default>
</entry>
<entry name="WordSeparator" type="string">
<label>Passphrase word separator</label>
<default>QStringLiteral("-")</default>
</entry>
<entry name="WordList" type="string">
<label>Passphrase word list</label>
<default></default>
</entry>
<entry name="WordCase" type="int">
<label>Passphrase word case</label>
<default>0</default>
</entry>
<entry name="GeneratorType" type="Enum">
<label>Passphrase word case</label>
<choices>
<choice name="Password">
<label>Password</label>
</choice>
<choice name="Passphrase">
<label>Passphrase</label>
</choice>
</choices>
<default>EnumGeneratorType::Password</default>
</entry>
</group>
</kcfg>
diff --git a/src/passentry.cpp b/src/passentry.cpp
index b1e1708..a86b57e 100644
--- a/src/passentry.cpp
+++ b/src/passentry.cpp
@@ -1,108 +1,114 @@
// 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-FileContributor: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include "passentry.h"
+#include "config.h"
namespace
{
bool isLineHidden(const QString &line)
{
return line.startsWith(QStringLiteral("otpauth://"), Qt::CaseInsensitive);
}
}
PassEntry::PassEntry()
{
}
bool PassEntry::isNull() const
{
return m_password.isEmpty() && m_remainingData.isEmpty();
}
+PassEntry::PassEntry(const QString &name, const QString &content)
+ : PassEntry(name, content, Config::templateEnabled() ? Config::passTemplate() : QStringList(), Config::templateEnabled() && Config::templateAllFields())
+{
+}
+
// TODO port to QStringView once we don't need to support Qt5 anymore
PassEntry::PassEntry(const QString &name, const QString &content, const QStringList &templateFields, bool allFields)
: m_name(name)
{
if (content.isEmpty()) {
return;
}
auto lines = content.split(QLatin1Char('\n'));
m_password = lines.takeFirst();
QStringList remainingData;
QStringList remainingDataForDisplay;
for (const QString &line : std::as_const(lines)) {
if (line.contains(QLatin1Char(':'))) {
int colon = line.indexOf(QLatin1Char(':'));
const QString name = line.left(colon);
const QString value = line.right(line.length() - colon - 1);
if ((allFields && !value.startsWith(QStringLiteral("//"))) // if value startswith // colon is probably from a url
|| templateFields.contains(name)) {
m_namedValues.append({name.trimmed(), value.trimmed()});
continue;
}
}
remainingData.append(line);
if (!isLineHidden(line)) {
remainingDataForDisplay.append(line);
}
}
m_remainingData = remainingData.join(u'\n');
m_remainingDataForDisplay = remainingDataForDisplay.join(u'\n');
}
QString PassEntry::name() const
{
return m_name;
}
QString PassEntry::password() const
{
return m_password;
}
PassEntry::NamedValues PassEntry::namedValues() const
{
return m_namedValues;
}
QString PassEntry::remainingData() const
{
return m_remainingData;
}
QString PassEntry::remainingDataForDisplay() const
{
return m_remainingDataForDisplay;
}
PassEntry::NamedValues::NamedValues()
: QList()
{
}
PassEntry::NamedValues::NamedValues(std::initializer_list<NamedValue> values)
: QList(values)
{
}
QString PassEntry::NamedValues::takeValue(const QString &name)
{
for (int i = 0; i < length(); ++i) {
if (at(i).name == name) {
return takeAt(i).value;
}
}
return QString();
}
bool operator==(const PassEntry::NamedValue &a, const PassEntry::NamedValue &b)
{
return a.name == b.name && a.value == b.value;
}
diff --git a/src/passentry.h b/src/passentry.h
index d183a08..dadd6b6 100644
--- a/src/passentry.h
+++ b/src/passentry.h
@@ -1,68 +1,70 @@
// SPDX-FileCopyrightText: 2024 g10 Code GmbH
// SPDX-FileContributor: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QList>
#include <QString>
/// This class represent a decrypted pass entry.
class PassEntry
{
public:
PassEntry();
+ explicit PassEntry(const QString &name, const QString &content);
+
/// \param templateFields the fields in the template. Fields in the
/// template will always be in namedValues() at the beginning of the
/// list in the same order.
///
/// \param allFields whether all fields should be considered as named
/// values. If set to false only templateFields are returned in
/// namedValues().
explicit PassEntry(const QString &name, const QString &content, const QStringList &templateFields, bool allFields);
struct NamedValue {
QString name;
QString value;
};
/// \brief The NamedValues class is mostly a list of NamedValue but also
/// has a method to take a specific NamedValue pair out of the list.
class NamedValues : public QList<NamedValue>
{
public:
NamedValues();
NamedValues(std::initializer_list<NamedValue> values);
QString takeValue(const QString &name);
};
bool isNull() const;
/// \return the name from the file name
QString name() const;
/// \return the password from the parsed file.
QString password() const;
/// \return the named values in the file in the order of appearence, with
/// template values first.
NamedValues namedValues() const;
/// \return the data that is not the password and not in namedValues.
QString remainingData() const;
/// Same as remainingData but without data that should not be displayed
/// (like a TOTP secret).
QString remainingDataForDisplay() const;
private:
QString m_name;
QString m_password;
QString m_remainingData;
QString m_remainingDataForDisplay;
NamedValues m_namedValues;
};
bool operator==(const PassEntry::NamedValue &a, const PassEntry::NamedValue &b);
diff --git a/src/settings.cpp b/src/settings.cpp
deleted file mode 100644
index adfe4db..0000000
--- a/src/settings.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- 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
-{
-#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
-constexpr QLatin1String passTemplate("passTemplate");
-constexpr QLatin1String useTemplate("useTemplate");
-constexpr QLatin1String templateAllFields("templateAllFields");
-#else
-static const QLatin1String passTemplate("passTemplate");
-static const QLatin1String useTemplate("useTemplate");
-static const QLatin1String templateAllFields("templateAllFields");
-#endif
-};
-
-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;
-}
-
-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/settings.h b/src/settings.h
deleted file mode 100644
index ba7b38b..0000000
--- a/src/settings.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- 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
-*/
-#ifndef SETTINGS_H
-#define SETTINGS_H
-
-#include <QByteArray>
-#include <QHash>
-#include <QPoint>
-#include <QSettings>
-#include <QSize>
-#include <QVariant>
-
-/*!
- \class Settings
- \brief Singleton that stores settings, saves and loads config
-*/
-class Settings : public QSettings
-{
-private:
- Settings(const QString &filename, const QSettings::Format format)
- : QSettings(filename, format)
- {
- }
- explicit Settings()
- : QSettings()
- {
- }
-
- static bool initialized;
- static Settings *m_instance;
- static Settings *getInstance();
-
-public:
- static QString getPassTemplate(const QString &defaultValue = {});
- static void setPassTemplate(const QString &passTemplate);
-
- static bool isUseTemplate(const bool &defaultValue = {});
- static void setUseTemplate(const bool &useTemplate);
-
- static bool isTemplateAllFields(const bool &defaultValue = {});
- static void setTemplateAllFields(const bool &templateAllFields);
-};
-
-#endif // SETTINGS_H
diff --git a/src/widgets/mainwindow.cpp b/src/widgets/mainwindow.cpp
index 6a1c1be..9cd8e4d 100644
--- a/src/widgets/mainwindow.cpp
+++ b/src/widgets/mainwindow.cpp
@@ -1,819 +1,813 @@
/*
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: 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 "mainwindow.h"
#include <gpgpass_version.h>
#include "clipboardhelper.h"
#include "conf/configuredialog.h"
#include "job/directoryreencryptjob.h"
#include "job/filedecryptjob.h"
#include "job/fileencryptjob.h"
#include "models/addfileinfoproxy.h"
#include "passentry.h"
#include "rootfoldersmanager.h"
-#include "settings.h"
#include "setupwidget.h"
#include "ui_mainwindow.h"
#include "usersdialog.h"
#include "util.h"
#include "widgets/passwordeditorwidget.h"
#include "widgets/passwordviewerwidget.h"
#include <KLocalizedString>
#include <KMessageBox>
#include <KMessageWidget>
#include <QCloseEvent>
#include <QComboBox>
#include <QDebug>
#include <QDirIterator>
#include <QFileInfo>
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
#include <QShortcut>
#include <qmessagebox.h>
enum class StackLayer {
WelcomePage = 0,
PasswordViewer = 1,
ConfigurationPage = 2,
PasswordEditor = 3,
};
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)
, ui(new Ui::MainWindow)
, m_clipboardHelper(new ClipboardHelper(this))
, m_passwordViewer(new PasswordViewerWidget(m_clipboardHelper, this))
, m_passwordEditor(new PasswordEditorWidget(m_clipboardHelper, this))
, m_rootFoldersManager(new RootFoldersManager(this))
{
#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);
ui->stackedWidget->addWidget(m_passwordViewer);
auto setupWidget = new SetupWidget(this);
ui->stackedWidget->addWidget(setupWidget);
ui->stackedWidget->addWidget(m_passwordEditor);
connect(setupWidget, &SetupWidget::setupComplete, this, &MainWindow::slotSetupFinished);
ui->actionEdit->setCheckable(true);
ui->separator->setFixedHeight(1);
ui->separator->setFrameStyle(QFrame::HLine);
ui->lineEditWrapper->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);
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
m_errorMessage->setPosition(KMessageWidget::Position::Header);
#endif
m_errorMessage->hide();
ui->messagesArea->addWidget(m_errorMessage);
m_storeModel.setRootFoldersManager(m_rootFoldersManager);
connect(&m_storeModel, &StoreModel::errorOccurred, this, [this](auto str) {
m_errorMessage->setText(str);
m_errorMessage->animatedShow();
setUiElementsEnabled(true);
});
}
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()));
ui->treeView->setModel(&m_storeModel);
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);
searchTimer.setInterval(350);
searchTimer.setSingleShot(true);
connect(&searchTimer, &QTimer::timeout, this, &MainWindow::onTimeoutSearch);
initToolBarButtons();
ui->lineEdit->setClearButtonEnabled(true);
setUiElementsEnabled(true);
QTimer::singleShot(10, this, SLOT(focusInput()));
verifyInitialized();
}
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::toggled, 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->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")));
}
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<ConfigureDialog> d(new ConfigureDialog(m_rootFoldersManager, this));
d->setModal(true);
if (d->exec()) {
if (d->result() == QDialog::Accepted) {
this->show();
m_passwordViewer->setPanelTimer();
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();
if (!file.isEmpty() && QFileInfo(file).isFile() && !cleared) {
m_selectedFile = file;
auto decryptJob = new FileDecryptJob(file);
connect(decryptJob, &FileDecryptJob::result, this, &MainWindow::fileDecryptedSlot);
decryptJob->start();
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordViewer);
} else {
m_passwordViewer->clear();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(true);
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
}
}
/**
* @brief MainWindow::on_treeView_doubleClicked when doubleclicked on
* TreeViewItem, open the edit Window
* @param index
*/
void MainWindow::editTreeItem(const QModelIndex &index)
{
QFileInfo fileInfo{index.data(QFileSystemModel::Roles::FilePathRole).toString()};
if (!fileInfo.isFile()) {
return;
}
auto decryptJob = new FileDecryptJob(fileInfo.absoluteFilePath());
connect(decryptJob, &FileDecryptJob::finished, this, [this, decryptJob](KJob *) {
if (decryptJob->error() != KJob::NoError) {
m_errorMessage->setText(decryptJob->errorText());
m_errorMessage->animatedShow();
return;
}
openPasswordDialog(decryptJob->filePath(), decryptJob->content());
});
decryptJob->start();
}
void MainWindow::openPasswordDialog(const QString &filePath, const QString &content)
{
PassEntry entry;
const auto name = QFileInfo(filePath).baseName();
if (!content.isEmpty()) {
- const auto templates = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QLatin1Char('\n')) : QStringList();
- const bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields();
-
- entry = PassEntry(name, content, templates, allFields);
+ entry = PassEntry(name, content);
}
m_passwordEditor->setPassEntry(entry);
ui->actionEdit->setChecked(true);
ui->stackedWidget->setCurrentIndex((int)StackLayer::PasswordEditor);
connect(m_passwordEditor, &PasswordEditorWidget::save, this, [this, filePath](const QString &content) {
const QFileInfo fileInfo(filePath);
const auto recipients = m_storeModel.recipientsForFile(fileInfo);
auto encryptJob = new FileEncryptJob(fileInfo.absoluteFilePath(), content.toUtf8(), recipients);
connect(encryptJob, &FileDecryptJob::finished, this, [encryptJob, this](KJob *) {
if (encryptJob->error() != KJob::NoError) {
// TODO use KMessageWidget in PasswordDialog instead
QMessageBox::critical(this, i18nc("@info:status", "Unable to save password"), encryptJob->errorText());
return;
}
ui->treeView->setFocus();
ui->actionEdit->setChecked(false);
selectTreeItem(getCurrentTreeViewIndex());
});
encryptJob->start();
});
connect(m_passwordEditor, &PasswordEditorWidget::editorClosed, this, [this]() {
selectTreeItem(getCurrentTreeViewIndex());
ui->actionEdit->setChecked(false);
});
}
/**
* @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);
m_passwordViewer->clear();
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
}
void MainWindow::fileDecryptedSlot(KJob *job)
{
auto decryptJob = qobject_cast<FileDecryptJob *>(job);
Q_ASSERT(decryptJob);
- const QStringList templ = Settings::isUseTemplate() ? Settings::getPassTemplate().split(QLatin1Char('\n')) : QStringList();
- const bool allFields = Settings::isUseTemplate() && Settings::isTemplateAllFields();
const auto content = decryptJob->content();
- const PassEntry entry(ui->treeView->selectionModel()->currentIndex().data().toString(), content, templ, allFields);
+ const PassEntry entry(ui->treeView->selectionModel()->currentIndex().data().toString(), content);
m_passwordViewer->setPassEntry(entry, content);
setUiElementsEnabled(true);
m_errorMessage->animatedHide();
}
/**
* @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->treeView->expandAll();
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);
m_storeModel.setFilterRegularExpression(regExp);
if (m_storeModel.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 (m_storeModel.rowCount() > 0) {
selectFirstFile();
selectTreeItem(ui->treeView->currentIndex());
}
}
/**
* @brief MainWindow::selectFirstFile select the first possible file in the
* tree
*/
void MainWindow::selectFirstFile()
{
auto model = ui->treeView->model();
auto index = firstFile(model->index(0, 0));
ui->treeView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
}
/**
* @brief MainWindow::firstFile return location of first possible file
* @param parentIndex
* @return QModelIndex
*/
QModelIndex MainWindow::firstFile(QModelIndex parentIndex)
{
auto model = parentIndex.model();
int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row) {
auto index = model->index(row, 0, parentIndex);
if (index.data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>().isFile())
return index;
if (model->hasChildren(index))
return firstFile(index);
}
return parentIndex;
}
QString MainWindow::fallbackStore()
{
const auto rootFolders = m_rootFoldersManager->rootFolders();
if (rootFolders.isEmpty()) {
QMessageBox::critical(this, i18nc("@title:dialog", "No password store found"), i18nc("@info", "Please add a password store first."));
return {};
}
return rootFolders[0]->path();
}
/**
* @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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
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"));
openPasswordDialog(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(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>");
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;
if (!isDir) {
QFile(file).remove();
} else {
QDir dir(file);
dir.removeRecursively();
}
}
/**
* @brief MainWindow::onEdit try and edit (selected) password.
*/
void MainWindow::onEdit(bool edit)
{
if (edit) {
editTreeItem(ui->treeView->currentIndex());
} else {
selectTreeItem(getCurrentTreeViewIndex());
ui->actionEdit->setChecked(false);
}
}
/**
* @brief MainWindow::userDialog see MainWindow::onUsers()
* @param dir folder to edit users for.
*/
void MainWindow::userDialog(QString dir)
{
if (dir.isEmpty()) {
dir = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
const auto recipients = m_storeModel.recipientsForFile(QFileInfo(dir));
auto usersDialog = new UsersDialog(recipients, this);
usersDialog->setAttribute(Qt::WA_DeleteOnClose);
connect(usersDialog, &UsersDialog::save, this, [dir, this, usersDialog](const QList<QByteArray> &recipients) {
auto reencryptJob = new DirectoryReencryptJob(m_storeModel, recipients, dir);
connect(reencryptJob, &DirectoryReencryptJob::result, this, [this, usersDialog](KJob *job) {
if (job->error() != KJob::NoError) {
usersDialog->showError(job->errorText());
return;
}
usersDialog->close();
setUiElementsEnabled(true);
ui->treeView->setFocus();
verifyInitialized();
});
// statusBar()->showMessage(i18n("Re-encrypting folders"), 3000);
setUiElementsEnabled(false);
ui->treeView->setDisabled(true);
reencryptJob->start();
});
usersDialog->show();
}
/**
* @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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
} else {
QFileInfo fi(dir);
if (!fi.isDir()) {
dir = fi.absolutePath();
}
dir = Util::normalizeFolderPath(dir);
}
userDialog(dir);
}
void MainWindow::slotSetupFinished(const QString &location, const QByteArray &keyId)
{
const QString gpgIdFile = location + QStringLiteral(".gpg-id");
QFile gpgId(gpgIdFile);
if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::error(this, i18n("Unable to write user configuration at \"%1\". Error: %2", gpgIdFile, gpgId.errorString()));
return;
}
gpgId.write(keyId);
gpgId.close();
m_rootFoldersManager->addRootFolder(i18nc("Default store name", "Local Store"), location);
m_rootFoldersManager->save();
ui->lineEdit->clear();
m_passwordViewer->clear();
ui->treeView->selectionModel()->clear();
ui->actionEdit->setEnabled(false);
ui->actionDelete->setEnabled(false);
ui->stackedWidget->setCurrentIndex((int)StackLayer::WelcomePage);
verifyInitialized();
}
void MainWindow::verifyInitialized()
{
auto alreadyConfigured = !m_rootFoldersManager->rootFolders().isEmpty();
ui->sidebar->setVisible(alreadyConfigured);
ui->stackedWidget->setCurrentIndex(alreadyConfigured ? (int)StackLayer::WelcomePage : (int)StackLayer::ConfigurationPage);
ui->actionAddFolder->setEnabled(alreadyConfigured);
ui->actionAddPassword->setEnabled(alreadyConfigured);
ui->actionDelete->setEnabled(ui->actionDelete->isEnabled() && alreadyConfigured);
ui->actionEdit->setEnabled(ui->actionEdit->isEnabled() && alreadyConfigured);
}
/**
* @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 (m_storeModel.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 = ui->treeView->currentIndex().data(AddFileInfoProxy::FileInfoRole).value<QFileInfo>();
QMenu contextMenu;
if (!selected || fileOrFolder.isDir()) {
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 = fallbackStore();
if (dir.isEmpty()) {
return;
}
}
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_storeModel.move(srcDir, destDir);
}
/**
* @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_storeModel.move(file, newFile);
}
/**
* @brief MainWindow::critical critical message popup wrapper.
* @param title
* @param msg
*/
void MainWindow::critical(const QString &title, const QString &msg)
{
QMessageBox::critical(this, title, msg);
}
diff --git a/src/widgets/passwordeditorwidget.cpp b/src/widgets/passwordeditorwidget.cpp
index 089ec10..440b3ce 100644
--- a/src/widgets/passwordeditorwidget.cpp
+++ b/src/widgets/passwordeditorwidget.cpp
@@ -1,158 +1,141 @@
/*
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 "passwordeditorwidget.h"
+#include "config.h"
#include "passentry.h"
#include "passwordgeneratorwidget.h"
-#include "settings.h"
#include "ui_passwordeditorwidget.h"
#include <QFileInfo>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
+#include <qalgorithms.h>
PasswordEditorWidget::PasswordEditorWidget(ClipboardHelper *clipboardHelper, QWidget *parent)
: QWidget(parent)
, ui(std::make_unique<Ui::PasswordEditorWidget>())
, m_clipboardHelper(clipboardHelper)
{
ui->setupUi(this);
connect(ui->createPasswordButton, &QAbstractButton::clicked, this, &PasswordEditorWidget::createPassword);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &PasswordEditorWidget::accept);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &PasswordEditorWidget::editorClosed);
}
void PasswordEditorWidget::setPassEntry(const PassEntry &passEntry)
{
m_passEntry = passEntry;
load();
}
PassEntry PasswordEditorWidget::passEntry() const
{
return m_passEntry;
}
/**
* @brief Pass{}{}wordDialog::~PasswordEditorWidget basic destructor.
*/
PasswordEditorWidget::~PasswordEditorWidget() = default;
/**
* @brief PasswordEditorWidget::on_createPasswordButton_clicked generate a random
* passwords.
* @todo refactor when process is untangled from MainWindow class.
*/
void PasswordEditorWidget::createPassword()
{
auto generator = PasswordGeneratorWidget::popupGenerator(m_clipboardHelper, this);
connect(generator, &PasswordGeneratorWidget::appliedPassword, this, [this](const QString &newPassword) {
ui->lineEditPassword->setPassword(newPassword);
});
}
void PasswordEditorWidget::accept()
{
Q_EMIT save(toRawContent());
Q_EMIT editorClosed();
}
/**
* @brief PasswordEditorWidget::setPassword populate the (templated) fields.
* @param password
*/
void PasswordEditorWidget::load()
{
+ // clear
while (ui->formLayout->rowCount() > 1) {
ui->formLayout->removeRow(1);
}
+ m_templateLines.clear();
+ m_otherLines.clear();
- ui->title->setText(m_passEntry.name());
-
- setTemplate(Settings::getPassTemplate(), Settings::isUseTemplate());
- templateAll(Settings::isTemplateAllFields());
+ m_fields = Config::self()->passTemplate();
+ // Setup password
+ ui->title->setText(m_passEntry.name());
ui->lineEditPassword->setPassword(m_passEntry.password());
+ // Setup defined templated values
QWidget *previous = ui->createPasswordButton;
- // first set templated values
- auto namedValues = m_passEntry.namedValues();
- 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 auto &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;
+ if (Config::self()->templateEnabled()) {
+ auto namedValues = m_passEntry.namedValues();
+ const auto fields = Config::self()->passTemplate();
+ for (const QString &field : std::as_const(fields)) {
+ if (field.isEmpty()) {
+ continue;
+ }
+
+ auto line = new QLineEdit();
+ line->setObjectName(field);
+ line->setText(namedValues.takeValue(field));
+ ui->formLayout->addRow(new QLabel(i18nc("Field name", "%1:", field)), line);
+ setTabOrder(previous, line);
+ m_templateLines.append(line);
+ previous = line;
+ }
+
+ // setup remaining values (if there are)
+ for (const auto &namedValue : std::as_const(namedValues)) {
+ auto line = new QLineEdit();
+ line->setObjectName(namedValue.name);
+ line->setText(namedValue.value);
+ ui->formLayout->addRow(new QLabel(namedValue.name), line);
+ setTabOrder(previous, line);
+ m_otherLines.append(line);
+ previous = line;
+ }
}
+ // setup notes
ui->plainTextEdit->insertPlainText(m_passEntry.remainingData());
}
QString PasswordEditorWidget::toRawContent() const
{
QString passFile = ui->lineEditPassword->password() + QLatin1Char('\n');
- QList<QLineEdit *> allLines(templateLines);
- allLines.append(otherLines);
+ QList<QLineEdit *> allLines(m_templateLines);
+ allLines.append(m_otherLines);
for (QLineEdit *line : allLines) {
const QString text = line->text();
- if (text.isEmpty())
+ if (text.isEmpty()) {
continue;
+ }
passFile += line->objectName() + QStringLiteral(": ") + text + QLatin1Char('\n');
}
passFile += ui->plainTextEdit->toPlainText();
- if (passFile.right(1) != QLatin1Char('\n'))
+ if (passFile.right(1) != QLatin1Char('\n')) {
passFile += QLatin1Char('\n');
- return passFile;
-}
-
-/**
- * @brief PasswordEditorWidget::setTemplate set the template and create the fields.
- * @param rawFields
- */
-void PasswordEditorWidget::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 PasswordEditorWidget::templateAll basic setter for use in
- * PasswordEditorWidget::setPassword templating all tokenisable lines.
- * @param templateAll
- */
-void PasswordEditorWidget::templateAll(bool templateAll)
-{
- m_allFields = templateAll;
+ return passFile;
}
diff --git a/src/widgets/passwordeditorwidget.h b/src/widgets/passwordeditorwidget.h
index 0acebaf..0fe27ef 100644
--- a/src/widgets/passwordeditorwidget.h
+++ b/src/widgets/passwordeditorwidget.h
@@ -1,73 +1,63 @@
/*
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 PasswordEditorWidget_H_
#define PasswordEditorWidget_H_
#include "passentry.h"
#include <QDialog>
namespace Ui
{
class PasswordEditorWidget;
}
class QLineEdit;
class ClipboardHelper;
/*!
\class PasswordEditorWidget
\brief PasswordEditorWidget Handles the inserting and editing of passwords.
Includes templated views.
*/
class PasswordEditorWidget : public QWidget
{
Q_OBJECT
public:
explicit PasswordEditorWidget(ClipboardHelper *clipboardHelper, QWidget *parent = nullptr);
~PasswordEditorWidget() override;
PassEntry passEntry() const;
void setPassEntry(const PassEntry &passEntry);
- /*! 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 templateAll(bool templateAll);
-
void accept();
Q_SIGNALS:
void save(const QString &content);
void editorClosed();
private Q_SLOTS:
void createPassword();
private:
void load();
QString toRawContent() const;
std::unique_ptr<Ui::PasswordEditorWidget> ui;
PassEntry m_passEntry;
ClipboardHelper *const m_clipboardHelper;
QStringList m_fields;
- bool m_templating;
- bool m_allFields;
- QList<QLineEdit *> templateLines;
- QList<QLineEdit *> otherLines;
+ QList<QLineEdit *> m_templateLines;
+ QList<QLineEdit *> m_otherLines;
};
#endif // PasswordEditorWidget_H_

File Metadata

Mime Type
text/x-diff
Expires
Thu, Feb 26, 6:43 PM (16 h, 4 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
90/c2/be99a34fd8a1aadd27de723275ea

Event Timeline