12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931 |
- /*
- * Copyright (C) 2019 Aetf <aetf@unlimitedcodeworks.xyz>
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 or (at your option)
- * version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include "TestGuiFdoSecrets.h"
- #include "fdosecrets/FdoSecretsPlugin.h"
- #include "fdosecrets/FdoSecretsSettings.h"
- #include "fdosecrets/objects/Collection.h"
- #include "fdosecrets/objects/Item.h"
- #include "fdosecrets/objects/SessionCipher.h"
- #include "fdosecrets/widgets/AccessControlDialog.h"
- #include "config-keepassx-tests.h"
- #include "core/Tools.h"
- #include "crypto/Crypto.h"
- #include "gui/Application.h"
- #include "gui/DatabaseTabWidget.h"
- #include "gui/FileDialog.h"
- #include "gui/MainWindow.h"
- #include "gui/MessageBox.h"
- #include "gui/PasswordWidget.h"
- #include "gui/wizard/NewDatabaseWizard.h"
- #include "util/FdoSecretsProxy.h"
- #include "util/TemporaryFile.h"
- #include <QCheckBox>
- #include <QLineEdit>
- #include <QSignalSpy>
- #include <QTest>
- #include <utility>
- int main(int argc, char* argv[])
- {
- #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
- QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
- QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
- #endif
- Application app(argc, argv);
- app.setApplicationName("KeePassXC");
- app.setApplicationVersion(KEEPASSXC_VERSION);
- app.setQuitOnLastWindowClosed(false);
- app.setAttribute(Qt::AA_Use96Dpi, true);
- app.applyTheme();
- QTEST_DISABLE_KEYPAD_NAVIGATION
- TestGuiFdoSecrets tc;
- QTEST_SET_MAIN_SOURCE_PATH
- return QTest::qExec(&tc, argc, argv);
- }
- #define DBUS_PATH_DEFAULT_ALIAS "/org/freedesktop/secrets/aliases/default"
- // assert macros compatible with function having return values
- #define VERIFY2_RET(statement, msg) \
- do { \
- if (!QTest::qVerify(static_cast<bool>(statement), #statement, (msg), __FILE__, __LINE__)) \
- return {}; \
- } while (false)
- #define COMPARE_RET(actual, expected) \
- do { \
- if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \
- return {}; \
- } while (false)
- // by default use these with Qt macros
- #define VERIFY QVERIFY
- #define COMPARE QCOMPARE
- #define VERIFY2 QVERIFY2
- #define DBUS_COMPARE(actual, expected) \
- do { \
- auto reply = (actual); \
- VERIFY2(reply.isValid(), reply.error().name().toLocal8Bit()); \
- COMPARE(reply.value(), (expected)); \
- } while (false)
- #define DBUS_VERIFY(stmt) \
- do { \
- auto reply = (stmt); \
- VERIFY2(reply.isValid(), reply.error().name().toLocal8Bit()); \
- } while (false)
- #define DBUS_GET(var, stmt) \
- std::remove_cv<decltype((stmt).argumentAt<0>())>::type var; \
- do { \
- const auto rep = (stmt); \
- VERIFY2(rep.isValid(), rep.error().name().toLocal8Bit()); \
- var = rep.argumentAt<0>(); \
- } while (false)
- #define DBUS_GET2(name1, name2, stmt) \
- std::remove_cv<decltype((stmt).argumentAt<0>())>::type name1; \
- std::remove_cv<decltype((stmt).argumentAt<1>())>::type name2; \
- do { \
- const auto rep = (stmt); \
- VERIFY2(rep.isValid(), rep.error().name().toLocal8Bit()); \
- name1 = rep.argumentAt<0>(); \
- name2 = rep.argumentAt<1>(); \
- } while (false)
- using namespace FdoSecrets;
- class FakeClient : public DBusClient
- {
- public:
- explicit FakeClient(DBusMgr* dbus)
- : DBusClient(
- dbus,
- {QStringLiteral("local"), 0, true, {ProcInfo{0, 0, QStringLiteral("fake-client"), QString{}, QString{}}}})
- {
- }
- };
- // pretty print QDBusObjectPath in QCOMPARE
- char* toString(const QDBusObjectPath& path)
- {
- return QTest::toString("ObjectPath(" + path.path() + ")");
- }
- TestGuiFdoSecrets::~TestGuiFdoSecrets() = default;
- void TestGuiFdoSecrets::initTestCase()
- {
- VERIFY(Crypto::init());
- Config::createTempFileInstance();
- config()->set(Config::AutoSaveAfterEveryChange, false);
- config()->set(Config::AutoSaveOnExit, false);
- config()->set(Config::GUI_ShowTrayIcon, true);
- config()->set(Config::UpdateCheckMessageShown, true);
- // Disable quick unlock
- config()->set(Config::Security_QuickUnlock, false);
- // Disable secret service integration (activate within individual tests to test the plugin)
- FdoSecrets::settings()->setEnabled(false);
- // activate within individual tests
- FdoSecrets::settings()->setShowNotification(false);
- Application::bootstrap();
- m_mainWindow.reset(new MainWindow());
- m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
- VERIFY(m_tabWidget);
- m_plugin = FdoSecretsPlugin::getPlugin();
- VERIFY(m_plugin);
- m_mainWindow->show();
- auto key = QByteArray::fromHex("e407997e8b918419cf851cf3345358fdf"
- "ffb9564a220ac9c3934efd277cea20d17"
- "467ecdc56e817f75ac39501f38a4a04ff"
- "64d627e16c09981c7ad876da255b61c8e"
- "6a8408236c2a4523cfe6961c26dbdfc77"
- "c1a27a5b425ca71a019e829fae32c0b42"
- "0e1b3096b48bc2ce9ccab1d1ff13a5eb4"
- "b263cee30bdb1a57af9bfa93f");
- m_clientCipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7(key));
- // Load the NewDatabase.kdbx file into temporary storage
- QFile sourceDbFile(QStringLiteral(KEEPASSX_TEST_DATA_DIR "/NewDatabase.kdbx"));
- VERIFY(sourceDbFile.open(QIODevice::ReadOnly));
- VERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData));
- sourceDbFile.close();
- // set a fake dbus client all the time so we can freely access DBusMgr anywhere
- m_client.reset(new FakeClient(m_plugin->dbus().data()));
- m_plugin->dbus()->overrideClient(m_client);
- }
- // Every test starts with opening the temp database
- void TestGuiFdoSecrets::init()
- {
- m_dbFile.reset(new TemporaryFile());
- // Write the temp storage to a temp database file for use in our tests
- VERIFY(m_dbFile->open());
- COMPARE(m_dbFile->write(m_dbData), static_cast<qint64>(m_dbData.size()));
- m_dbFile->close();
- // make sure window is activated or focus tests may fail
- m_mainWindow->activateWindow();
- processEvents();
- // open and unlock the database
- m_tabWidget->addDatabaseTab(m_dbFile->fileName(), false, "a");
- m_dbWidget = m_tabWidget->currentDatabaseWidget();
- m_db = m_dbWidget->database();
- // by default expose the root group
- FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid());
- VERIFY(m_dbWidget->save());
- // enforce consistent default settings at the beginning
- FdoSecrets::settings()->setUnlockBeforeSearch(false);
- FdoSecrets::settings()->setShowNotification(false);
- FdoSecrets::settings()->setConfirmAccessItem(false);
- FdoSecrets::settings()->setEnabled(false);
- }
- // Every test ends with closing the temp database without saving
- void TestGuiFdoSecrets::cleanup()
- {
- // restore to default settings
- FdoSecrets::settings()->setUnlockBeforeSearch(false);
- FdoSecrets::settings()->setShowNotification(false);
- FdoSecrets::settings()->setConfirmAccessItem(false);
- FdoSecrets::settings()->setEnabled(false);
- if (m_plugin) {
- m_plugin->updateServiceState();
- }
- // DO NOT save the database
- for (int i = 0; i != m_tabWidget->count(); ++i) {
- m_tabWidget->databaseWidgetFromIndex(i)->database()->markAsClean();
- }
- // Close any dialogs
- while (auto w = QApplication::activeModalWidget()) {
- w->close();
- }
- VERIFY(m_tabWidget->closeAllDatabaseTabs());
- processEvents();
- if (m_dbFile) {
- m_dbFile->remove();
- }
- m_client->clearAuthorization();
- }
- void TestGuiFdoSecrets::cleanupTestCase()
- {
- m_plugin->dbus()->overrideClient({});
- if (m_dbFile) {
- m_dbFile->remove();
- }
- }
- void TestGuiFdoSecrets::testServiceEnable()
- {
- QSignalSpy sigError(m_plugin, SIGNAL(error(QString)));
- VERIFY(sigError.isValid());
- QSignalSpy sigStarted(m_plugin, SIGNAL(secretServiceStarted()));
- VERIFY(sigStarted.isValid());
- // make sure no one else is holding the service
- VERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(DBUS_SERVICE_SECRET));
- // enable the service
- auto service = enableService();
- VERIFY(service);
- // service started without error
- VERIFY(sigError.isEmpty());
- COMPARE(sigStarted.size(), 1);
- processEvents();
- VERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(DBUS_SERVICE_SECRET));
- // there will be one default collection
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- DBUS_COMPARE(coll->locked(), false);
- DBUS_COMPARE(coll->label(), m_db->metadata()->name());
- DBUS_COMPARE(coll->created(),
- static_cast<qulonglong>(m_db->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000));
- DBUS_COMPARE(
- coll->modified(),
- static_cast<qulonglong>(m_db->rootGroup()->timeInfo().lastModificationTime().toMSecsSinceEpoch() / 1000));
- }
- void TestGuiFdoSecrets::testServiceEnableNoExposedDatabase()
- {
- // reset the exposed group and then enable the service
- FdoSecrets::settings()->setExposedGroup(m_db, {});
- auto service = enableService();
- VERIFY(service);
- // no collections
- DBUS_COMPARE(service->collections(), QList<QDBusObjectPath>{});
- }
- void TestGuiFdoSecrets::testServiceSearch()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- entry->attributes()->set("fdosecrets-test", "1");
- entry->attributes()->set("fdosecrets-test-protected", "2", true);
- const QString crazyKey = "_a:bc&-+'-e%12df_d";
- const QString crazyValue = "[v]al@-ue";
- entry->attributes()->set(crazyKey, crazyValue);
- // search by title
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", entry->title()}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {QDBusObjectPath(item->path())});
- }
- // search by attribute
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"fdosecrets-test", "1"}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {QDBusObjectPath(item->path())});
- }
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{crazyKey, crazyValue}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {QDBusObjectPath(item->path())});
- }
- // searching using empty terms returns nothing
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({}));
- COMPARE(locked, {});
- COMPARE(unlocked, {});
- }
- // searching using protected attributes or password returns nothing
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Password", entry->password()}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {});
- }
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"fdosecrets-test-protected", "2"}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {});
- }
- }
- void TestGuiFdoSecrets::testServiceSearchBlockingUnlock()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto entries = m_db->rootGroup()->entriesRecursive();
- VERIFY(!entries.isEmpty());
- // assumes the db is not empty
- auto title = entries.first()->title();
- // NOTE: entries are no longer valid after locking
- lockDatabaseInBackend();
- // when database is locked, nothing is returned
- FdoSecrets::settings()->setUnlockBeforeSearch(false);
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", title}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {});
- }
- // when database is locked, nothing is returned
- FdoSecrets::settings()->setUnlockBeforeSearch(true);
- {
- // SearchItems will block because the blocking wait is implemented
- // using a local QEventLoop.
- // so we do a little trick here to get the return value back
- bool unlockDialogWorks = false;
- QTimer::singleShot(50, [&]() { unlockDialogWorks = driveUnlockDialog(); });
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", title}}));
- VERIFY(unlockDialogWorks);
- COMPARE(locked, {});
- COMPARE(unlocked.size(), 1);
- auto item = getProxy<ItemProxy>(unlocked.first());
- DBUS_COMPARE(item->label(), title);
- }
- }
- void TestGuiFdoSecrets::testServiceSearchBlockingUnlockMultiple()
- {
- // setup: two databases, both locked, one with exposed db, the other not.
- // add another database tab with a database with no exposed group
- // to avoid modify the original, copy to a temp file first
- QFile sourceDbFile(QStringLiteral(KEEPASSX_TEST_DATA_DIR "/NewDatabase2.kdbx"));
- QByteArray dbData;
- VERIFY(sourceDbFile.open(QIODevice::ReadOnly));
- VERIFY(Tools::readAllFromDevice(&sourceDbFile, dbData));
- sourceDbFile.close();
- QTemporaryFile anotherFile;
- VERIFY(anotherFile.open());
- COMPARE(anotherFile.write(dbData), static_cast<qint64>(dbData.size()));
- anotherFile.close();
- m_tabWidget->addDatabaseTab(anotherFile.fileName(), false);
- auto anotherWidget = m_tabWidget->currentDatabaseWidget();
- auto service = enableService();
- VERIFY(service);
- // when there are multiple locked databases,
- // repeatly show the dialog until there is at least one unlocked collection
- FdoSecrets::settings()->setUnlockBeforeSearch(true);
- // when only unlocking the one with no exposed group, a second dialog is shown
- lockDatabaseInBackend();
- {
- bool unlockDialogWorks = false;
- QTimer::singleShot(50, [&]() {
- unlockDialogWorks = driveUnlockDialog(anotherWidget);
- QTimer::singleShot(50, [&]() { unlockDialogWorks &= driveUnlockDialog(); });
- });
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", "Sample Entry"}}));
- VERIFY(unlockDialogWorks);
- COMPARE(locked, {});
- COMPARE(unlocked.size(), 1);
- }
- // when unlocking the one with exposed group, the other one remains locked
- lockDatabaseInBackend();
- {
- bool unlockDialogWorks = false;
- QTimer::singleShot(50, [&]() { unlockDialogWorks = driveUnlockDialog(m_dbWidget); });
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", "Sample Entry"}}));
- VERIFY(unlockDialogWorks);
- COMPARE(locked, {});
- COMPARE(unlocked.size(), 1);
- VERIFY(anotherWidget->isLocked());
- }
- }
- void TestGuiFdoSecrets::testServiceSearchForce()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- // fdosecrets should still find the item even if searching is disabled
- entry->group()->setSearchingEnabled(Group::Disable);
- // search by title
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", entry->title()}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {QDBusObjectPath(item->path())});
- }
- }
- void TestGuiFdoSecrets::testServiceUnlock()
- {
- lockDatabaseInBackend();
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- QSignalSpy spyCollectionCreated(service.data(), SIGNAL(CollectionCreated(QDBusObjectPath)));
- VERIFY(spyCollectionCreated.isValid());
- QSignalSpy spyCollectionDeleted(service.data(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
- VERIFY(spyCollectionDeleted.isValid());
- QSignalSpy spyCollectionChanged(service.data(), SIGNAL(CollectionChanged(QDBusObjectPath)));
- VERIFY(spyCollectionChanged.isValid());
- DBUS_GET2(unlocked, promptPath, service->Unlock({QDBusObjectPath(coll->path())}));
- // nothing is unlocked immediately without user's action
- COMPARE(unlocked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // nothing is unlocked yet
- VERIFY(waitForSignal(spyPromptCompleted, 0));
- DBUS_COMPARE(coll->locked(), true);
- // show the prompt
- DBUS_VERIFY(prompt->Prompt(""));
- // still not unlocked before user action
- VERIFY(waitForSignal(spyPromptCompleted, 0));
- DBUS_COMPARE(coll->locked(), true);
- VERIFY(driveUnlockDialog());
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- }
- // check unlocked *AFTER* the prompt signal
- DBUS_COMPARE(coll->locked(), false);
- VERIFY(waitForSignal(spyCollectionCreated, 0));
- QTRY_VERIFY(!spyCollectionChanged.isEmpty());
- for (const auto& args : spyCollectionChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), coll->path());
- }
- VERIFY(waitForSignal(spyCollectionDeleted, 0));
- }
- void TestGuiFdoSecrets::testServiceUnlockDatabaseConcurrent()
- {
- lockDatabaseInBackend();
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- DBUS_GET2(unlocked, promptPath, service->Unlock({QDBusObjectPath(coll->path())}));
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- DBUS_VERIFY(prompt->Prompt(""));
- // while the first prompt is running, another request come in
- DBUS_GET2(unlocked2, promptPath2, service->Unlock({QDBusObjectPath(coll->path())}));
- auto prompt2 = getProxy<PromptProxy>(promptPath2);
- VERIFY(prompt2);
- QSignalSpy spyPromptCompleted2(prompt2.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted2.isValid());
- DBUS_VERIFY(prompt2->Prompt(""));
- // there should be only one unlock dialog
- VERIFY(driveUnlockDialog());
- // both prompts should complete
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- }
- VERIFY(waitForSignal(spyPromptCompleted2, 1));
- {
- auto args = spyPromptCompleted2.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- }
- // check unlocked *AFTER* prompt signal
- DBUS_COMPARE(coll->locked(), false);
- }
- void TestGuiFdoSecrets::testServiceUnlockItems()
- {
- FdoSecrets::settings()->setConfirmAccessItem(true);
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- DBUS_COMPARE(item->locked(), true);
- {
- DBUS_GET2(unlocked, promptPath, service->Unlock({QDBusObjectPath(item->path())}));
- // nothing is unlocked immediately without user's action
- COMPARE(unlocked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // nothing is unlocked yet
- COMPARE(spyPromptCompleted.count(), 0);
- DBUS_COMPARE(item->locked(), true);
- // drive the prompt
- DBUS_VERIFY(prompt->Prompt(""));
- // only allow once
- VERIFY(driveAccessControlDialog(false));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(item->path())});
- }
- // unlocked
- DBUS_COMPARE(item->locked(), false);
- }
- // access the secret should reset the locking state
- {
- DBUS_GET(ss, item->GetSecret(QDBusObjectPath(sess->path())));
- }
- DBUS_COMPARE(item->locked(), true);
- // unlock again with remember
- {
- DBUS_GET2(unlocked, promptPath, service->Unlock({QDBusObjectPath(item->path())}));
- // nothing is unlocked immediately without user's action
- COMPARE(unlocked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // nothing is unlocked yet
- COMPARE(spyPromptCompleted.count(), 0);
- DBUS_COMPARE(item->locked(), true);
- // drive the prompt
- DBUS_VERIFY(prompt->Prompt(""));
- // only allow and remember
- VERIFY(driveAccessControlDialog(true));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(item->path())});
- }
- // unlocked
- DBUS_COMPARE(item->locked(), false);
- }
- // access the secret does not reset the locking state
- {
- DBUS_GET(ss, item->GetSecret(QDBusObjectPath(sess->path())));
- }
- DBUS_COMPARE(item->locked(), false);
- }
- void TestGuiFdoSecrets::testServiceUnlockItemsIncludeFutureEntries()
- {
- FdoSecrets::settings()->setConfirmAccessItem(true);
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- DBUS_COMPARE(item->locked(), true);
- {
- DBUS_GET2(unlocked, promptPath, service->Unlock({QDBusObjectPath(item->path())}));
- // nothing is unlocked immediately without user's action
- COMPARE(unlocked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // nothing is unlocked yet
- COMPARE(spyPromptCompleted.count(), 0);
- DBUS_COMPARE(item->locked(), true);
- // drive the prompt
- DBUS_VERIFY(prompt->Prompt(""));
- // remember and include future entries
- VERIFY(driveAccessControlDialog(true, true));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(item->path())});
- }
- // unlocked
- DBUS_COMPARE(item->locked(), false);
- }
- // check other entries are also unlocked
- {
- DBUS_GET(itemPaths, coll->items());
- VERIFY(itemPaths.size() > 1);
- auto anotherItem = getProxy<ItemProxy>(itemPaths.last());
- VERIFY(anotherItem);
- DBUS_COMPARE(anotherItem->locked(), false);
- }
- }
- void TestGuiFdoSecrets::testServiceLock()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- QSignalSpy spyCollectionCreated(service.data(), SIGNAL(CollectionCreated(QDBusObjectPath)));
- VERIFY(spyCollectionCreated.isValid());
- QSignalSpy spyCollectionDeleted(service.data(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
- VERIFY(spyCollectionDeleted.isValid());
- QSignalSpy spyCollectionChanged(service.data(), SIGNAL(CollectionChanged(QDBusObjectPath)));
- VERIFY(spyCollectionChanged.isValid());
- // if the db is modified, prompt user
- m_db->markAsModified();
- {
- DBUS_GET2(locked, promptPath, service->Lock({QDBusObjectPath(coll->path())}));
- COMPARE(locked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // prompt and click cancel
- MessageBox::setNextAnswer(MessageBox::Cancel);
- DBUS_VERIFY(prompt->Prompt(""));
- processEvents();
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), true);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {});
- DBUS_COMPARE(coll->locked(), false);
- }
- {
- DBUS_GET2(locked, promptPath, service->Lock({QDBusObjectPath(coll->path())}));
- COMPARE(locked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // prompt and click save
- MessageBox::setNextAnswer(MessageBox::Save);
- DBUS_VERIFY(prompt->Prompt(""));
- processEvents();
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- DBUS_COMPARE(coll->locked(), true);
- }
- VERIFY(waitForSignal(spyCollectionCreated, 0));
- QTRY_VERIFY(!spyCollectionChanged.isEmpty());
- for (const auto& args : spyCollectionChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), coll->path());
- }
- VERIFY(waitForSignal(spyCollectionDeleted, 0));
- // locking item locks the whole db
- unlockDatabaseInBackend();
- {
- auto item = getFirstItem(coll);
- DBUS_GET2(locked, promptPath, service->Lock({QDBusObjectPath(item->path())}));
- COMPARE(locked, {});
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- MessageBox::setNextAnswer(MessageBox::Save);
- DBUS_VERIFY(prompt->Prompt(""));
- processEvents();
- DBUS_COMPARE(coll->locked(), true);
- }
- }
- void TestGuiFdoSecrets::testServiceLockConcurrent()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- m_db->markAsModified();
- DBUS_GET2(locked, promptPath, service->Lock({QDBusObjectPath(coll->path())}));
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- DBUS_GET2(locked2, promptPath2, service->Lock({QDBusObjectPath(coll->path())}));
- auto prompt2 = getProxy<PromptProxy>(promptPath2);
- VERIFY(prompt2);
- QSignalSpy spyPromptCompleted2(prompt2.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted2.isValid());
- // prompt and click save
- MessageBox::setNextAnswer(MessageBox::Save);
- DBUS_VERIFY(prompt->Prompt(""));
- // second prompt should not show dialog
- DBUS_VERIFY(prompt2->Prompt(""));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- }
- VERIFY(waitForSignal(spyPromptCompleted2, 1));
- {
- auto args = spyPromptCompleted2.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(getSignalVariantArgument<QList<QDBusObjectPath>>(args.at(1)), {QDBusObjectPath(coll->path())});
- }
- DBUS_COMPARE(coll->locked(), true);
- }
- void TestGuiFdoSecrets::testSessionOpen()
- {
- auto service = enableService();
- VERIFY(service);
- auto sess = openSession(service, PlainCipher::Algorithm);
- VERIFY(sess);
- sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- }
- void TestGuiFdoSecrets::testSessionClose()
- {
- auto service = enableService();
- VERIFY(service);
- auto sess = openSession(service, PlainCipher::Algorithm);
- VERIFY(sess);
- DBUS_VERIFY(sess->Close());
- }
- void TestGuiFdoSecrets::testCollectionCreate()
- {
- auto service = enableService();
- VERIFY(service);
- QSignalSpy spyCollectionCreated(service.data(), SIGNAL(CollectionCreated(QDBusObjectPath)));
- VERIFY(spyCollectionCreated.isValid());
- // returns existing if alias is nonempty and exists
- {
- auto existing = getDefaultCollection(service);
- DBUS_GET2(collPath,
- promptPath,
- service->CreateCollection({{DBUS_INTERFACE_SECRET_COLLECTION + ".Label", "NewDB"}}, "default"));
- COMPARE(promptPath, QDBusObjectPath("/"));
- COMPARE(collPath.path(), existing->path());
- }
- VERIFY(waitForSignal(spyCollectionCreated, 0));
- // create new one and set properties
- {
- DBUS_GET2(collPath,
- promptPath,
- service->CreateCollection({{DBUS_INTERFACE_SECRET_COLLECTION + ".Label", "Test NewDB"}}, "mydatadb"));
- COMPARE(collPath, QDBusObjectPath("/"));
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- DBUS_VERIFY(prompt->Prompt(""));
- VERIFY(driveNewDatabaseWizard());
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- auto coll = getProxy<CollectionProxy>(getSignalVariantArgument<QDBusObjectPath>(args.at(1)));
- VERIFY(coll);
- DBUS_COMPARE(coll->label(), QStringLiteral("Test NewDB"));
- VERIFY(waitForSignal(spyCollectionCreated, 1));
- {
- args = spyCollectionCreated.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), coll->path());
- }
- }
- }
- void TestGuiFdoSecrets::testCollectionDelete()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- // save the path which will be gone after the deletion.
- auto collPath = coll->path();
- QSignalSpy spyCollectionDeleted(service.data(), SIGNAL(CollectionDeleted(QDBusObjectPath)));
- VERIFY(spyCollectionDeleted.isValid());
- m_db->markAsModified();
- DBUS_GET(promptPath, coll->Delete());
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // prompt and click save
- MessageBox::setNextAnswer(MessageBox::Save);
- DBUS_VERIFY(prompt->Prompt(""));
- // closing the tab should have deleted the database if not in testing
- // but deleteLater is not processed in QApplication::processEvent
- // see https://doc.qt.io/qt-5/qcoreapplication.html#processEvents
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(args.at(1).value<QDBusVariant>().variant().toString(), QStringLiteral(""));
- // however, the object should already be taken down from dbus
- {
- auto reply = coll->locked();
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::UnknownObject);
- }
- VERIFY(waitForSignal(spyCollectionDeleted, 1));
- {
- args = spyCollectionDeleted.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), collPath);
- }
- }
- void TestGuiFdoSecrets::testCollectionDeleteConcurrent()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- m_db->markAsModified();
- DBUS_GET(promptPath, coll->Delete());
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // before interacting with the prompt, another request come in
- DBUS_GET(promptPath2, coll->Delete());
- auto prompt2 = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt2);
- QSignalSpy spyPromptCompleted2(prompt2.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted2.isValid());
- // prompt and click save
- MessageBox::setNextAnswer(MessageBox::Save);
- DBUS_VERIFY(prompt->Prompt(""));
- // there should be no prompt
- DBUS_VERIFY(prompt2->Prompt(""));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- {
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(args.at(1).value<QDBusVariant>().variant().toString(), QStringLiteral(""));
- }
- VERIFY(waitForSignal(spyPromptCompleted2, 1));
- {
- auto args = spyPromptCompleted2.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(args.at(1).value<QDBusVariant>().variant().toString(), QStringLiteral(""));
- }
- {
- auto reply = coll->locked();
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::UnknownObject);
- }
- }
- void TestGuiFdoSecrets::testCollectionChange()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- QSignalSpy spyCollectionChanged(service.data(), SIGNAL(CollectionChanged(QDBusObjectPath)));
- VERIFY(spyCollectionChanged.isValid());
- DBUS_VERIFY(coll->setLabel("anotherLabel"));
- COMPARE(m_db->metadata()->name(), QStringLiteral("anotherLabel"));
- QTRY_COMPARE(spyCollectionChanged.size(), 1);
- {
- auto args = spyCollectionChanged.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), coll->path());
- }
- }
- void TestGuiFdoSecrets::testHiddenFilename()
- {
- // when file name contains leading dot, all parts excepting the last should be used
- // for collection name, and the registration should success
- VERIFY(m_dbFile->rename(QFileInfo(*m_dbFile).path() + "/.Name.kdbx"));
- // reset is necessary to not hold database longer and cause connections
- // not cleaned up when the database tab is closed.
- m_db.reset();
- VERIFY(m_tabWidget->closeAllDatabaseTabs());
- m_tabWidget->addDatabaseTab(m_dbFile->fileName(), false, "a");
- m_dbWidget = m_tabWidget->currentDatabaseWidget();
- m_db = m_dbWidget->database();
- // enable the service
- auto service = enableService();
- VERIFY(service);
- // collection is properly registered
- auto coll = getDefaultCollection(service);
- auto collObj = m_plugin->dbus()->pathToObject<Collection>(QDBusObjectPath(coll->path()));
- VERIFY(collObj);
- COMPARE(collObj->name(), QStringLiteral(".Name"));
- }
- void TestGuiFdoSecrets::testDuplicateName()
- {
- QTemporaryDir dir;
- VERIFY(dir.isValid());
- // create another file under different path but with the same filename
- QString anotherFile = dir.path() + "/" + QFileInfo(*m_dbFile).fileName();
- m_dbFile->copy(anotherFile);
- m_tabWidget->addDatabaseTab(anotherFile, false, "a");
- auto service = enableService();
- VERIFY(service);
- // when two databases have the same name, one of it will have part of its uuid suffixed
- const QString pathNoSuffix = QStringLiteral("/org/freedesktop/secrets/collection/KeePassXC");
- DBUS_GET(colls, service->collections());
- COMPARE(colls.size(), 2);
- COMPARE(colls[0].path(), pathNoSuffix);
- VERIFY(colls[1].path() != pathNoSuffix);
- }
- void TestGuiFdoSecrets::testItemCreate()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- QSignalSpy spyItemCreated(coll.data(), SIGNAL(ItemCreated(QDBusObjectPath)));
- VERIFY(spyItemCreated.isValid());
- // create item
- StringStringMap attributes{
- {"application", "fdosecrets-test"},
- {"attr-i[bute]", "![some] -value*"},
- };
- auto item = createItem(sess, coll, "abc", "Password", attributes, false);
- VERIFY(item);
- // signals
- {
- VERIFY(waitForSignal(spyItemCreated, 1));
- auto args = spyItemCreated.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item->path());
- }
- // attributes
- {
- DBUS_GET(actual, item->attributes());
- for (const auto& key : attributes.keys()) {
- COMPARE(actual[key], attributes[key]);
- }
- }
- // label
- DBUS_COMPARE(item->label(), QStringLiteral("abc"));
- // secrets
- {
- DBUS_GET(ss, item->GetSecret(QDBusObjectPath(sess->path())));
- auto decrypted = m_clientCipher->decrypt(ss.unmarshal(m_plugin->dbus()));
- COMPARE(decrypted.value, QByteArrayLiteral("Password"));
- }
- // searchable
- {
- DBUS_GET2(unlocked, locked, service->SearchItems(attributes));
- COMPARE(locked, {});
- COMPARE(unlocked, {QDBusObjectPath(item->path())});
- }
- {
- DBUS_GET(unlocked, coll->SearchItems(attributes));
- VERIFY(unlocked.contains(QDBusObjectPath(item->path())));
- }
- }
- void TestGuiFdoSecrets::testItemCreateUnlock()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- // NOTE: entries are no longer valid after locking
- lockDatabaseInBackend();
- QSignalSpy spyItemCreated(coll.data(), SIGNAL(ItemCreated(QDBusObjectPath)));
- VERIFY(spyItemCreated.isValid());
- // create item
- StringStringMap attributes{
- {"application", "fdosecrets-test"},
- {"attr-i[bute]", "![some] -value*"},
- };
- auto item = createItem(sess, coll, "abc", "Password", attributes, false, false, true);
- VERIFY(item);
- }
- void TestGuiFdoSecrets::testItemChange()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- QSignalSpy spyItemChanged(coll.data(), SIGNAL(ItemChanged(QDBusObjectPath)));
- VERIFY(spyItemChanged.isValid());
- DBUS_VERIFY(item->setLabel("anotherLabel"));
- COMPARE(entry->title(), QStringLiteral("anotherLabel"));
- QTRY_VERIFY(!spyItemChanged.isEmpty());
- for (const auto& args : spyItemChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item->path());
- }
- spyItemChanged.clear();
- DBUS_VERIFY(item->setAttributes({
- {"abc", "def"},
- }));
- COMPARE(entry->attributes()->value("abc"), QStringLiteral("def"));
- QTRY_VERIFY(!spyItemChanged.isEmpty());
- for (const auto& args : spyItemChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item->path());
- }
- }
- void TestGuiFdoSecrets::testItemReplace()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- // create item
- StringStringMap attr1{
- {"application", "fdosecrets-test"},
- {"attr-i[bute]", "![some] -value*"},
- {"fdosecrets-attr", "1"},
- };
- StringStringMap attr2{
- {"application", "fdosecrets-test"},
- {"attr-i[bute]", "![some] -value*"},
- {"fdosecrets-attr", "2"},
- };
- auto item1 = createItem(sess, coll, "abc1", "Password", attr1, false);
- VERIFY(item1);
- auto item2 = createItem(sess, coll, "abc2", "Password", attr2, false);
- VERIFY(item2);
- {
- DBUS_GET2(unlocked, locked, service->SearchItems({{"application", "fdosecrets-test"}}));
- QSet<QDBusObjectPath> expected{QDBusObjectPath(item1->path()), QDBusObjectPath(item2->path())};
- COMPARE(QSet<QDBusObjectPath>::fromList(unlocked), expected);
- }
- QSignalSpy spyItemCreated(coll.data(), SIGNAL(ItemCreated(QDBusObjectPath)));
- VERIFY(spyItemCreated.isValid());
- QSignalSpy spyItemChanged(coll.data(), SIGNAL(ItemChanged(QDBusObjectPath)));
- VERIFY(spyItemChanged.isValid());
- {
- // when replace, existing item with matching attr is updated
- auto item3 = createItem(sess, coll, "abc3", "Password", attr2, true);
- VERIFY(item3);
- COMPARE(item2->path(), item3->path());
- DBUS_COMPARE(item3->label(), QStringLiteral("abc3"));
- // there are still 2 entries
- DBUS_GET2(unlocked, locked, service->SearchItems({{"application", "fdosecrets-test"}}));
- QSet<QDBusObjectPath> expected{QDBusObjectPath(item1->path()), QDBusObjectPath(item2->path())};
- COMPARE(QSet<QDBusObjectPath>::fromList(unlocked), expected);
- VERIFY(waitForSignal(spyItemCreated, 0));
- // there may be multiple changed signals, due to each item attribute is set separately
- QTRY_VERIFY(!spyItemChanged.isEmpty());
- for (const auto& args : spyItemChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item3->path());
- }
- }
- spyItemCreated.clear();
- spyItemChanged.clear();
- {
- // when NOT replace, another entry is created
- auto item4 = createItem(sess, coll, "abc4", "Password", attr2, false);
- VERIFY(item4);
- DBUS_COMPARE(item2->label(), QStringLiteral("abc3"));
- DBUS_COMPARE(item4->label(), QStringLiteral("abc4"));
- // there are 3 entries
- DBUS_GET2(unlocked, locked, service->SearchItems({{"application", "fdosecrets-test"}}));
- QSet<QDBusObjectPath> expected{
- QDBusObjectPath(item1->path()),
- QDBusObjectPath(item2->path()),
- QDBusObjectPath(item4->path()),
- };
- COMPARE(QSet<QDBusObjectPath>::fromList(unlocked), expected);
- VERIFY(waitForSignal(spyItemCreated, 1));
- {
- auto args = spyItemCreated.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item4->path());
- }
- // there may be multiple changed signals, due to each item attribute is set separately
- VERIFY(!spyItemChanged.isEmpty());
- for (const auto& args : spyItemChanged) {
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), item4->path());
- }
- }
- }
- void TestGuiFdoSecrets::testItemReplaceExistingLocked()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- // create item
- StringStringMap attr1{
- {"application", "fdosecrets-test"},
- {"attr-i[bute]", "![some] -value*"},
- {"fdosecrets-attr", "1"},
- };
- auto item = createItem(sess, coll, "abc1", "Password", attr1, false);
- VERIFY(item);
- // make sure the item is locked
- {
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- FdoSecrets::settings()->setConfirmAccessItem(true);
- m_client->setItemAuthorized(entry->uuid(), AuthDecision::Undecided);
- DBUS_COMPARE(item->locked(), true);
- }
- // when replace with a locked item, there will be a prompt
- auto item2 = createItem(sess, coll, "abc2", "PasswordUpdated", attr1, true, true);
- VERIFY(item2);
- COMPARE(item2->path(), item->path());
- DBUS_COMPARE(item2->label(), QStringLiteral("abc2"));
- }
- void TestGuiFdoSecrets::testItemSecret()
- {
- const QString TEXT_PLAIN = "text/plain";
- const QString APPLICATION_OCTET_STREAM = "application/octet-stream";
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- // plain text secret
- {
- DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path())));
- auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus()));
- COMPARE(ss.contentType, TEXT_PLAIN);
- COMPARE(ss.value, entry->password().toUtf8());
- }
- // get secret with notification
- FdoSecrets::settings()->setShowNotification(true);
- {
- QSignalSpy spyShowNotification(m_plugin, SIGNAL(requestShowNotification(QString, QString, int)));
- VERIFY(spyShowNotification.isValid());
- DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path())));
- auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus()));
- COMPARE(ss.contentType, TEXT_PLAIN);
- COMPARE(ss.value, entry->password().toUtf8());
- COMPARE(ss.contentType, TEXT_PLAIN);
- COMPARE(ss.value, entry->password().toUtf8());
- VERIFY(waitForSignal(spyShowNotification, 1));
- }
- FdoSecrets::settings()->setShowNotification(false);
- // set secret with plain text
- {
- // first create Secret in wire format,
- // then convert to internal format and encrypt
- // finally convert encrypted internal format back to wire format to pass to SetSecret
- const QByteArray expected = QByteArrayLiteral("NewPassword");
- auto encrypted = encryptPassword(expected, TEXT_PLAIN, sess);
- DBUS_VERIFY(item->SetSecret(encrypted));
- COMPARE(entry->password().toUtf8(), expected);
- }
- // set secret with something else is saved as attachment
- const QByteArray expected = QByteArrayLiteral("NewPasswordBinary");
- {
- auto encrypted = encryptPassword(expected, APPLICATION_OCTET_STREAM, sess);
- DBUS_VERIFY(item->SetSecret(encrypted));
- COMPARE(entry->password(), QStringLiteral(""));
- }
- {
- DBUS_GET(encrypted, item->GetSecret(QDBusObjectPath(sess->path())));
- auto ss = m_clientCipher->decrypt(encrypted.unmarshal(m_plugin->dbus()));
- COMPARE(ss.contentType, APPLICATION_OCTET_STREAM);
- COMPARE(ss.value, expected);
- }
- }
- void TestGuiFdoSecrets::testItemDelete()
- {
- FdoSecrets::settings()->setConfirmDeleteItem(true);
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- // save the path which will be gone after the deletion.
- auto itemPath = item->path();
- QSignalSpy spyItemDeleted(coll.data(), SIGNAL(ItemDeleted(QDBusObjectPath)));
- VERIFY(spyItemDeleted.isValid());
- DBUS_GET(promptPath, item->Delete());
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // prompt and click save
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- MessageBox::setNextAnswer(MessageBox::Delete);
- DBUS_VERIFY(prompt->Prompt(""));
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.count(), 2);
- COMPARE(args.at(0).toBool(), false);
- COMPARE(args.at(1).toString(), QStringLiteral(""));
- VERIFY(waitForSignal(spyItemDeleted, 1));
- args = spyItemDeleted.takeFirst();
- COMPARE(args.size(), 1);
- COMPARE(args.at(0).value<QDBusObjectPath>().path(), itemPath);
- }
- void TestGuiFdoSecrets::testItemLockState()
- {
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- auto itemObj = m_plugin->dbus()->pathToObject<Item>(QDBusObjectPath(item->path()));
- VERIFY(itemObj);
- auto entry = itemObj->backend();
- VERIFY(entry);
- auto secret =
- wire::Secret{
- QDBusObjectPath(sess->path()),
- {},
- "NewPassword",
- "text/plain",
- }
- .unmarshal(m_plugin->dbus());
- auto encrypted = m_clientCipher->encrypt(secret).marshal();
- // when access confirmation is disabled, item is unlocked when the collection is unlocked
- FdoSecrets::settings()->setConfirmAccessItem(false);
- DBUS_COMPARE(item->locked(), false);
- // when access confirmation is enabled, item is locked if the client has no authorization
- FdoSecrets::settings()->setConfirmAccessItem(true);
- DBUS_COMPARE(item->locked(), true);
- // however, item properties are still accessible as long as the collection is unlocked
- DBUS_VERIFY(item->attributes());
- DBUS_VERIFY(item->setAttributes({}));
- DBUS_VERIFY(item->label());
- DBUS_VERIFY(item->setLabel("abc"));
- DBUS_VERIFY(item->created());
- DBUS_VERIFY(item->modified());
- // except secret, which is locked
- {
- auto reply = item->GetSecret(QDBusObjectPath(sess->path()));
- VERIFY(reply.isError());
- COMPARE(reply.error().name(), DBUS_ERROR_SECRET_IS_LOCKED);
- }
- {
- auto reply = item->SetSecret(encrypted);
- VERIFY(reply.isError());
- COMPARE(reply.error().name(), DBUS_ERROR_SECRET_IS_LOCKED);
- }
- // item is unlocked if the client is authorized
- m_client->setItemAuthorized(entry->uuid(), AuthDecision::Allowed);
- DBUS_COMPARE(item->locked(), false);
- DBUS_VERIFY(item->GetSecret(QDBusObjectPath(sess->path())));
- DBUS_VERIFY(item->SetSecret(encrypted));
- }
- void TestGuiFdoSecrets::testItemRejectSetReferenceFields()
- {
- // expose a subgroup, entries in it should not be able to retrieve data from entries outside it
- auto rootEntry = m_db->rootGroup()->entries().first();
- VERIFY(rootEntry);
- auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup");
- VERIFY(subgroup);
- FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid());
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- auto item = getFirstItem(coll);
- VERIFY(item);
- auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm);
- VERIFY(sess);
- const auto refText = QStringLiteral("{REF:P@T:%1}").arg(rootEntry->title());
- // reject ref in label
- {
- auto reply = item->setLabel(refText);
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::InvalidArgs);
- }
- // reject ref in custom attributes
- {
- auto reply = item->setAttributes({{"steal", refText}});
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::InvalidArgs);
- }
- // reject ref in password
- {
- auto reply = item->SetSecret(encryptPassword(refText.toUtf8(), "text/plain", sess));
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::InvalidArgs);
- }
- // reject ref in content type
- {
- auto reply = item->SetSecret(encryptPassword("dummy", refText, sess));
- VERIFY(reply.isFinished() && reply.isError());
- COMPARE(reply.error().type(), QDBusError::InvalidArgs);
- }
- }
- void TestGuiFdoSecrets::testAlias()
- {
- auto service = enableService();
- VERIFY(service);
- // read default alias
- DBUS_GET(collPath, service->ReadAlias("default"));
- auto coll = getProxy<CollectionProxy>(collPath);
- VERIFY(coll);
- // set extra alias
- DBUS_VERIFY(service->SetAlias("another", QDBusObjectPath(collPath)));
- // get using extra alias
- DBUS_GET(collPath2, service->ReadAlias("another"));
- COMPARE(collPath2, collPath);
- }
- void TestGuiFdoSecrets::testDefaultAliasAlwaysPresent()
- {
- auto service = enableService();
- VERIFY(service);
- // one collection, which is default alias
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- // after locking, the collection is still there, but locked
- lockDatabaseInBackend();
- coll = getDefaultCollection(service);
- VERIFY(coll);
- DBUS_COMPARE(coll->locked(), true);
- // unlock the database, the alias and collection is present
- unlockDatabaseInBackend();
- coll = getDefaultCollection(service);
- VERIFY(coll);
- DBUS_COMPARE(coll->locked(), false);
- }
- void TestGuiFdoSecrets::testExposeSubgroup()
- {
- auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup");
- VERIFY(subgroup);
- FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid());
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- // exposing subgroup does not expose entries in other groups
- DBUS_GET(itemPaths, coll->items());
- QSet<Entry*> exposedEntries;
- for (const auto& itemPath : itemPaths) {
- exposedEntries << m_plugin->dbus()->pathToObject<Item>(itemPath)->backend();
- }
- COMPARE(exposedEntries, QSet<Entry*>::fromList(subgroup->entries()));
- }
- void TestGuiFdoSecrets::testModifyingExposedGroup()
- {
- // test when exposed group is removed the collection is not exposed anymore
- auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking");
- VERIFY(subgroup);
- FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid());
- auto service = enableService();
- VERIFY(service);
- {
- DBUS_GET(collPaths, service->collections());
- COMPARE(collPaths.size(), 1);
- }
- m_db->metadata()->setRecycleBinEnabled(true);
- m_db->recycleGroup(subgroup);
- processEvents();
- {
- DBUS_GET(collPaths, service->collections());
- COMPARE(collPaths, {});
- }
- // test setting another exposed group, the collection will be exposed again
- FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid());
- processEvents();
- {
- DBUS_GET(collPaths, service->collections());
- COMPARE(collPaths.size(), 1);
- }
- }
- void TestGuiFdoSecrets::testNoExposeRecycleBin()
- {
- // when the recycle bin is underneath the exposed group
- // be careful not to expose entries in there
- FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid());
- m_db->metadata()->setRecycleBinEnabled(true);
- auto entry = m_db->rootGroup()->entries().first();
- VERIFY(entry);
- m_db->recycleEntry(entry);
- processEvents();
- auto service = enableService();
- VERIFY(service);
- auto coll = getDefaultCollection(service);
- VERIFY(coll);
- // exposing subgroup does not expose entries in other groups
- DBUS_GET(itemPaths, coll->items());
- QSet<Entry*> exposedEntries;
- for (const auto& itemPath : itemPaths) {
- exposedEntries << m_plugin->dbus()->pathToObject<Item>(itemPath)->backend();
- }
- VERIFY(!exposedEntries.contains(entry));
- // searching should not return the entry
- DBUS_GET2(unlocked, locked, service->SearchItems({{"Title", entry->title()}}));
- COMPARE(locked, {});
- COMPARE(unlocked, {});
- }
- void TestGuiFdoSecrets::lockDatabaseInBackend()
- {
- m_tabWidget->lockDatabases();
- m_db.reset();
- processEvents();
- }
- void TestGuiFdoSecrets::unlockDatabaseInBackend()
- {
- m_dbWidget->performUnlockDatabase("a");
- m_db = m_dbWidget->database();
- processEvents();
- }
- void TestGuiFdoSecrets::processEvents()
- {
- // Couldn't use QApplication::processEvents, because per Qt documentation:
- // events that are posted while the function runs will be queued until a later round of event processing.
- // and we may post QTimer single shot events during event handling to achieve async method.
- // So we directly call event dispatcher in a loop until no events can be handled
- while (QAbstractEventDispatcher::instance()->processEvents(QEventLoop::AllEvents)) {
- // pass
- }
- }
- // the following functions have return value, switch macros to the version supporting that
- #undef VERIFY
- #undef VERIFY2
- #undef COMPARE
- #define VERIFY(stmt) VERIFY2_RET(stmt, "")
- #define VERIFY2 VERIFY2_RET
- #define COMPARE COMPARE_RET
- QSharedPointer<ServiceProxy> TestGuiFdoSecrets::enableService()
- {
- FdoSecrets::settings()->setEnabled(true);
- VERIFY(m_plugin);
- m_plugin->updateServiceState();
- return getProxy<ServiceProxy>(QDBusObjectPath(DBUS_PATH_SECRETS));
- }
- QSharedPointer<SessionProxy> TestGuiFdoSecrets::openSession(const QSharedPointer<ServiceProxy>& service,
- const QString& algo)
- {
- VERIFY(service);
- if (algo == PlainCipher::Algorithm) {
- DBUS_GET2(output, sessPath, service->OpenSession(algo, QDBusVariant("")));
- return getProxy<SessionProxy>(sessPath);
- } else if (algo == DhIetf1024Sha256Aes128CbcPkcs7::Algorithm) {
- DBUS_GET2(output, sessPath, service->OpenSession(algo, QDBusVariant(m_clientCipher->negotiationOutput())));
- m_clientCipher->updateClientPublicKey(output.variant().toByteArray());
- return getProxy<SessionProxy>(sessPath);
- }
- QTest::qFail("Unsupported algorithm", __FILE__, __LINE__);
- return {};
- }
- QSharedPointer<CollectionProxy> TestGuiFdoSecrets::getDefaultCollection(const QSharedPointer<ServiceProxy>& service)
- {
- VERIFY(service);
- DBUS_GET(collPath, service->ReadAlias("default"));
- return getProxy<CollectionProxy>(collPath);
- }
- QSharedPointer<ItemProxy> TestGuiFdoSecrets::getFirstItem(const QSharedPointer<CollectionProxy>& coll)
- {
- VERIFY(coll);
- DBUS_GET(itemPaths, coll->items());
- VERIFY(!itemPaths.isEmpty());
- return getProxy<ItemProxy>(itemPaths.first());
- }
- QSharedPointer<ItemProxy> TestGuiFdoSecrets::createItem(const QSharedPointer<SessionProxy>& sess,
- const QSharedPointer<CollectionProxy>& coll,
- const QString& label,
- const QString& pass,
- const StringStringMap& attr,
- bool replace,
- bool expectPrompt,
- bool expectUnlockPrompt)
- {
- VERIFY(sess);
- VERIFY(coll);
- QVariantMap properties{
- {DBUS_INTERFACE_SECRET_ITEM + ".Label", QVariant::fromValue(label)},
- {DBUS_INTERFACE_SECRET_ITEM + ".Attributes", QVariant::fromValue(attr)},
- };
- wire::Secret ss;
- ss.session = QDBusObjectPath(sess->path());
- ss.value = pass.toLocal8Bit();
- ss.contentType = "plain/text";
- auto encrypted = m_clientCipher->encrypt(ss.unmarshal(m_plugin->dbus())).marshal();
- DBUS_GET2(itemPath, promptPath, coll->CreateItem(properties, encrypted, replace));
- auto prompt = getProxy<PromptProxy>(promptPath);
- VERIFY(prompt);
- QSignalSpy spyPromptCompleted(prompt.data(), SIGNAL(Completed(bool, QDBusVariant)));
- VERIFY(spyPromptCompleted.isValid());
- // drive the prompt
- DBUS_VERIFY(prompt->Prompt(""));
- bool unlockFound = driveUnlockDialog();
- COMPARE(unlockFound, expectUnlockPrompt);
- bool found = driveAccessControlDialog();
- COMPARE(found, expectPrompt);
- VERIFY(waitForSignal(spyPromptCompleted, 1));
- auto args = spyPromptCompleted.takeFirst();
- COMPARE(args.size(), 2);
- COMPARE(args.at(0).toBool(), false);
- itemPath = getSignalVariantArgument<QDBusObjectPath>(args.at(1));
- return getProxy<ItemProxy>(itemPath);
- }
- FdoSecrets::wire::Secret
- TestGuiFdoSecrets::encryptPassword(QByteArray value, QString contentType, const QSharedPointer<SessionProxy>& sess)
- {
- wire::Secret ss;
- ss.contentType = std::move(contentType);
- ss.value = std::move(value);
- ss.session = QDBusObjectPath(sess->path());
- return m_clientCipher->encrypt(ss.unmarshal(m_plugin->dbus())).marshal();
- }
- bool TestGuiFdoSecrets::driveAccessControlDialog(bool remember, bool includeFutureEntries)
- {
- processEvents();
- for (auto w : QApplication::topLevelWidgets()) {
- if (!w->isWindow()) {
- continue;
- }
- auto dlg = qobject_cast<AccessControlDialog*>(w);
- if (dlg && dlg->isVisible()) {
- auto rememberCheck = dlg->findChild<QCheckBox*>("rememberCheck");
- VERIFY(rememberCheck);
- rememberCheck->setChecked(remember);
- if (includeFutureEntries) {
- dlg->done(AccessControlDialog::AllowAll);
- } else {
- dlg->done(AccessControlDialog::AllowSelected);
- }
- processEvents();
- VERIFY(dlg->isHidden());
- return true;
- }
- }
- return false;
- }
- bool TestGuiFdoSecrets::driveNewDatabaseWizard()
- {
- // processEvents will block because the NewDatabaseWizard is shown using exec
- // which creates a local QEventLoop.
- // so we do a little trick here to get the return value back
- bool ret = false;
- QTimer::singleShot(0, this, [this, &ret]() {
- ret = [this]() -> bool {
- auto wizard = m_tabWidget->findChild<NewDatabaseWizard*>();
- VERIFY(wizard);
- COMPARE(wizard->currentId(), 0);
- wizard->next();
- wizard->next();
- COMPARE(wizard->currentId(), 2);
- // enter password
- auto* passwordEdit =
- wizard->findChild<PasswordWidget*>("enterPasswordEdit")->findChild<QLineEdit*>("passwordEdit");
- auto* passwordRepeatEdit =
- wizard->findChild<PasswordWidget*>("repeatPasswordEdit")->findChild<QLineEdit*>("passwordEdit");
- VERIFY(passwordEdit);
- VERIFY(passwordRepeatEdit);
- QTest::keyClicks(passwordEdit, "test");
- QTest::keyClick(passwordEdit, Qt::Key::Key_Tab);
- QTest::keyClicks(passwordRepeatEdit, "test");
- // save database to temporary file
- TemporaryFile tmpFile;
- VERIFY(tmpFile.open());
- tmpFile.close();
- fileDialog()->setNextFileName(tmpFile.fileName());
- // click Continue on the warning due to weak password
- MessageBox::setNextAnswer(MessageBox::ContinueWithWeakPass);
- wizard->accept();
- tmpFile.remove();
- return true;
- }();
- });
- processEvents();
- return ret;
- }
- bool TestGuiFdoSecrets::driveUnlockDialog(DatabaseWidget* target)
- {
- processEvents();
- auto dbOpenDlg = m_tabWidget->findChild<DatabaseOpenDialog*>();
- VERIFY(dbOpenDlg);
- if (!dbOpenDlg->isVisible()) {
- return false;
- }
- dbOpenDlg->setActiveDatabaseTab(target);
- auto editPassword = dbOpenDlg->findChild<PasswordWidget*>("editPassword")->findChild<QLineEdit*>("passwordEdit");
- VERIFY(editPassword);
- editPassword->setFocus();
- QTest::keyClicks(editPassword, "a");
- QTest::keyClick(editPassword, Qt::Key_Enter);
- processEvents();
- return true;
- }
- bool TestGuiFdoSecrets::waitForSignal(QSignalSpy& spy, int expectedCount)
- {
- processEvents();
- // If already expected count, do not wait and return immediately
- if (spy.count() == expectedCount) {
- return true;
- } else if (spy.count() > expectedCount) {
- return false;
- }
- spy.wait();
- COMPARE(spy.count(), expectedCount);
- return true;
- }
- #undef VERIFY
- #define VERIFY QVERIFY
- #undef COMPARE
- #define COMPARE QCOMPARE
|