From a5531a0350d5b5887427e341062b7f9b7e3127f2 Mon Sep 17 00:00:00 2001 From: Timon Reinold Date: Wed, 9 Jul 2025 16:57:48 +0200 Subject: [PATCH] Test configured FdoSecrets aliases Verify that configured Freedesktop Secret Service aliases are exposed on the DBus interface. Also run some tests against SettingsAliasesModel, using Qt's QAbstractItemModelTester, which tries to find violations of QAbstractItemModel's invariants. Including that we apparently shouldn't return flags for invalid indices, so check the index's validity in SettingsAliasesModel::flags(). --- src/fdosecrets/widgets/SettingsModels.cpp | 4 + tests/gui/TestGuiFdoSecrets.cpp | 194 ++++++++++++++++++++++ tests/gui/TestGuiFdoSecrets.h | 6 + 3 files changed, 204 insertions(+) diff --git a/src/fdosecrets/widgets/SettingsModels.cpp b/src/fdosecrets/widgets/SettingsModels.cpp index e7b12b477..3146b7697 100644 --- a/src/fdosecrets/widgets/SettingsModels.cpp +++ b/src/fdosecrets/widgets/SettingsModels.cpp @@ -497,6 +497,10 @@ namespace FdoSecrets Qt::ItemFlags SettingsAliasesModel::flags(const QModelIndex& index) const { + if (!index.isValid() || index.model() != this || index.row() >= rowCount({}) + || index.column() >= columnCount({})) { + return {}; + } // all table cells are editable (see SettingsAliasesModel::setData()) return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } diff --git a/tests/gui/TestGuiFdoSecrets.cpp b/tests/gui/TestGuiFdoSecrets.cpp index fc7e218ea..319d26a36 100644 --- a/tests/gui/TestGuiFdoSecrets.cpp +++ b/tests/gui/TestGuiFdoSecrets.cpp @@ -29,6 +29,7 @@ #include "core/Global.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "fdosecrets/widgets/SettingsModels.h" #include "gui/Application.h" #include "gui/DatabaseTabWidget.h" #include "gui/FileDialog.h" @@ -43,6 +44,7 @@ #include #include #include +#include #include int main(int argc, char* argv[]) @@ -202,6 +204,7 @@ void TestGuiFdoSecrets::init() VERIFY(m_dbWidget->save()); // enforce consistent default settings at the beginning + FdoSecrets::settings()->setCollectionAliases({}); FdoSecrets::settings()->setUnlockBeforeSearch(false); FdoSecrets::settings()->setShowNotification(false); FdoSecrets::settings()->setConfirmAccessItem(false); @@ -212,6 +215,7 @@ void TestGuiFdoSecrets::init() void TestGuiFdoSecrets::cleanup() { // restore to default settings + FdoSecrets::settings()->setCollectionAliases({}); FdoSecrets::settings()->setUnlockBeforeSearch(false); FdoSecrets::settings()->setShowNotification(false); FdoSecrets::settings()->setConfirmAccessItem(false); @@ -1602,6 +1606,196 @@ void TestGuiFdoSecrets::testDefaultAliasAlwaysPresent() DBUS_COMPARE(coll->locked(), false); } +void TestGuiFdoSecrets::testConfiguredAlias() +{ + + const QString newalias = "newalias"; + + auto service = enableService(); + VERIFY(service); + + DBUS_GET(prePath, service->ReadAlias(newalias)); + COMPARE(prePath, QDBusObjectPath("/")); + + FdoSecrets::settings()->setCollectionAlias(newalias, m_db->publicUuid()); + + DBUS_GET(collPath, service->ReadAlias(newalias)); + auto coll = getProxy(collPath); + VERIFY(coll); + + FdoSecrets::settings()->removeCollectionAlias(newalias, m_db->publicUuid()); + + DBUS_GET(postPath, service->ReadAlias(newalias)); + COMPARE(postPath, QDBusObjectPath("/")); +} + +void TestGuiFdoSecrets::testConfiguredDefaultAlias() +{ + const QString defaultAlias = "default"; + + auto service = enableService(); + VERIFY(service); + + // read original default alias + DBUS_GET(oldCollPath, service->ReadAlias(defaultAlias)); + // create a second collection (selects the new db) + QDBusObjectPath newCollPath; + { + QSignalSpy spyCollectionCreated(service.data(), SIGNAL(CollectionCreated(QDBusObjectPath))); + VERIFY(spyCollectionCreated.isValid()); + + DBUS_GET2(collPath, + promptPath, + service->CreateCollection({{DBUS_INTERFACE_SECRET_COLLECTION + ".Label", "Test NewDB"}}, "mydatadb")); + auto prompt = getProxy(promptPath); + DBUS_VERIFY(prompt->Prompt("")); + VERIFY(driveNewDatabaseWizard()); + + VERIFY(waitForSignal(spyCollectionCreated, 1)); + auto args = spyCollectionCreated.takeFirst(); + COMPARE(args.size(), 1); + newCollPath = args.at(0).value(); + } + VERIFY2(oldCollPath.path() != newCollPath.path(), oldCollPath.path().toStdString().data()); + COMPARE(m_tabWidget->count(), 2); + COMPARE(m_tabWidget->currentIndex(), 1); + + // read original default alias + DBUS_GET(origDefaultPath, service->ReadAlias(defaultAlias)); + COMPARE(origDefaultPath, newCollPath); + + // change default alias (back to the old db) + FdoSecrets::settings()->setCollectionAlias(defaultAlias, m_db->publicUuid()); + + // verify configuration had effect + DBUS_GET(configuredDefaultPath, service->ReadAlias(defaultAlias)); + COMPARE(configuredDefaultPath, oldCollPath); + auto configuredDefaultColl = getProxy(configuredDefaultPath); + VERIFY(configuredDefaultColl); + // verify, that the default alias no longer follows the active tab + m_tabWidget->setCurrentIndex(0); + DBUS_GET(configuredDefaultPath0, service->ReadAlias(defaultAlias)); + COMPARE(configuredDefaultPath0, oldCollPath); + m_tabWidget->setCurrentIndex(1); + DBUS_GET(configuredDefaultPath1, service->ReadAlias(defaultAlias)); + COMPARE(configuredDefaultPath1, oldCollPath); + + // remove default alias configuration + FdoSecrets::settings()->removeCollectionAlias(defaultAlias, m_db->publicUuid()); + + // verify that original default alias got restored + DBUS_GET(restoredDefaultPath, service->ReadAlias(defaultAlias)); + COMPARE(restoredDefaultPath, origDefaultPath); + // ... and changes with the active tab + m_tabWidget->setCurrentIndex(0); + DBUS_GET(restoredDefaultPath0, service->ReadAlias(defaultAlias)); + COMPARE(restoredDefaultPath0, oldCollPath); + m_tabWidget->setCurrentIndex(1); + DBUS_GET(restoredDefaultPath1, service->ReadAlias(defaultAlias)); + COMPARE(restoredDefaultPath1, newCollPath); +} + +void TestGuiFdoSecrets::testConfiguredUnavailableAlias() +{ + const QString alias = "unavailableAlias"; + auto service = enableService(); + VERIFY(service); + // configure an alias, for which the database is not currently opened + FdoSecrets::settings()->setCollectionAlias(alias, QUuid::createUuid()); + // check that this alias is not exposed + DBUS_GET(path, service->ReadAlias(alias)); + COMPARE(path, QDBusObjectPath("/")); +} + +void TestGuiFdoSecrets::testSettingsAliasesModelDisplay() +{ + FdoSecrets::SettingsAliasesModel model{m_tabWidget}; + QAbstractItemModelTester tester{&model}; + + model.setAliases({{"alias", m_db->publicUuid()}}); + QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), m_dbWidget->displayName()); + QCOMPARE(model.data(model.index(0, 1), Qt::ToolTipRole), m_db->filePath()); + QCOMPARE(model.data(model.index(0, 1), Qt::EditRole), m_db->publicUuid()); +} + +void TestGuiFdoSecrets::testSettingsAliasesModel() +{ + FdoSecrets::SettingsAliasesModel model{m_tabWidget}; + QAbstractItemModelTester tester{&model}; + + const QVariant defaultUuid = QUuid::createUuid(); + const QVariant otherUuid = QUuid::createUuid(); + const QVariant newUuid = QUuid::createUuid(); + const QVariantMap origAliases{ + {"default", defaultUuid}, + {"otherAlias", otherUuid}, + }; + model.setAliases(origAliases); + QCOMPARE(model.aliases(), origAliases); + // last row should be empty (to be filled with new entries) + QCOMPARE(model.rowCount({}), origAliases.size() + 1); + const QModelIndex nextAliasIdx = model.index(origAliases.size(), 0); + const QModelIndex nextUuidIdx = model.index(origAliases.size(), 1); + QCOMPARE(model.data(nextAliasIdx, Qt::DisplayRole), QVariant()); + QCOMPARE(model.data(nextUuidIdx, Qt::DisplayRole), QVariant()); + // last row should be editable (to insert new entries) + QVERIFY(model.flags(nextAliasIdx) & Qt::ItemIsEditable); + QVERIFY(model.setData(nextAliasIdx, "newAlias")); + QVERIFY(model.aliases().contains("newAlias")); + // aliases sorted, so this should be newAlias + const QModelIndex newAliasIdx = model.index(1, 0); + const QModelIndex newUuidIdx = model.index(1, 1); + QCOMPARE(model.data(newAliasIdx, Qt::DisplayRole), "newAlias"); + QVERIFY(model.flags(newUuidIdx) & Qt::ItemIsEditable); + QVERIFY(model.setData(newUuidIdx, newUuid)); + QCOMPARE(model.aliases()["newAlias"], newUuid); + // entries can be renamed + QVERIFY(model.flags(newAliasIdx) & Qt::ItemIsEditable); + QCOMPARE(model.data(newAliasIdx, Qt::EditRole), "newAlias"); + QVERIFY(model.setData(newAliasIdx, "renamedAlias")); + QVERIFY(!model.aliases().contains("newAlias")); + // updates data model.aliases() + QCOMPARE(model.aliases()["default"], defaultUuid); + QCOMPARE(model.aliases()["otherAlias"], otherUuid); + QCOMPARE(model.aliases()["renamedAlias"], newUuid); + // updates sorted display + QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "default"); + QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "otherAlias"); + QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), "renamedAlias"); + QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), defaultUuid); + QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), otherUuid); + QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), newUuid); + + // automatically generate names for newly inserted databases + const QVariant unnamedUuid = QUuid::createUuid(); + QVERIFY(model.setData(model.index(3, 1), unnamedUuid)); + QCOMPARE(model.aliases()["alias1"], unnamedUuid); // "default" already exists + // updates sorted display + QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "alias1"); + QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "default"); + QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), "otherAlias"); + QCOMPARE(model.data(model.index(3, 0), Qt::DisplayRole), "renamedAlias"); + QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), unnamedUuid); + QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), defaultUuid); + QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), otherUuid); + QCOMPARE(model.data(model.index(3, 1), Qt::DisplayRole), newUuid); + + // remove rows + model.removeRow(1); + QCOMPARE(model.rowCount({}), 4); + QVERIFY(!model.aliases().contains("default")); + // other way of removing rows + QVERIFY(model.setData(model.index(1, 0), "")); + QCOMPARE(model.rowCount({}), 3); + QVERIFY(!model.aliases().contains("otherAlias")); + QCOMPARE(model.data(model.index(0, 0), Qt::DisplayRole), "alias1"); + QCOMPARE(model.data(model.index(1, 0), Qt::DisplayRole), "renamedAlias"); + QCOMPARE(model.data(model.index(2, 0), Qt::DisplayRole), {}); + QCOMPARE(model.data(model.index(0, 1), Qt::DisplayRole), unnamedUuid); + QCOMPARE(model.data(model.index(1, 1), Qt::DisplayRole), newUuid); + QCOMPARE(model.data(model.index(2, 1), Qt::DisplayRole), {}); +} + void TestGuiFdoSecrets::testExposeSubgroup() { auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup"); diff --git a/tests/gui/TestGuiFdoSecrets.h b/tests/gui/TestGuiFdoSecrets.h index 1624eed49..55754842d 100644 --- a/tests/gui/TestGuiFdoSecrets.h +++ b/tests/gui/TestGuiFdoSecrets.h @@ -96,6 +96,12 @@ private slots: void testAlias(); void testDefaultAliasAlwaysPresent(); + void testConfiguredAlias(); + void testConfiguredDefaultAlias(); + void testConfiguredUnavailableAlias(); + + void testSettingsAliasesModelDisplay(); + void testSettingsAliasesModel(); void testExposeSubgroup(); void testModifyingExposedGroup();