Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F36623275
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
66 KB
Subscribers
None
View Options
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
Details
Attached
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
Attached To
rGPGPASS GnuPG Password Manager
Event Timeline
Log In to Comment