From 44779bc862d9219ac3ab1b8e4537ec4fbf8e4239 Mon Sep 17 00:00:00 2001 From: Aetf Date: Mon, 16 Dec 2019 16:49:58 -0500 Subject: FdoSecrets: add unit tests --- tests/CMakeLists.txt | 7 +- .../org.freedesktop.Secret.Collection.xml | 33 + .../interfaces/org.freedesktop.Secret.Item.xml | 21 + .../interfaces/org.freedesktop.Secret.Prompt.xml | 11 + .../interfaces/org.freedesktop.Secret.Service.xml | 55 + .../interfaces/org.freedesktop.Secret.Session.xml | 4 + tests/data/dbus/session.conf | 39 + tests/gui/CMakeLists.txt | 9 + tests/gui/TestGuiFdoSecrets.cpp | 1175 ++++++++++++++++++++ tests/gui/TestGuiFdoSecrets.h | 121 ++ 10 files changed, 1472 insertions(+), 3 deletions(-) create mode 100644 tests/data/dbus/interfaces/org.freedesktop.Secret.Collection.xml create mode 100644 tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml create mode 100644 tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml create mode 100644 tests/data/dbus/interfaces/org.freedesktop.Secret.Service.xml create mode 100644 tests/data/dbus/interfaces/org.freedesktop.Secret.Session.xml create mode 100644 tests/data/dbus/session.conf create mode 100644 tests/gui/TestGuiFdoSecrets.cpp create mode 100644 tests/gui/TestGuiFdoSecrets.h (limited to 'tests') diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e1bfaac13..28d8516cf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -57,8 +57,9 @@ macro(parse_arguments prefix arg_names option_names) endmacro(parse_arguments) macro(add_unit_test) - parse_arguments(TEST "NAME;SOURCES;LIBS" "" ${ARGN}) + parse_arguments(TEST "NAME;SOURCES;LIBS;LAUNCHER" "" ${ARGN}) set(_test_NAME ${TEST_NAME}) + set(_test_LAUNCHER ${TEST_LAUNCHER}) set(_srcList ${TEST_SOURCES}) add_executable(${_test_NAME} ${_srcList}) target_link_libraries(${_test_NAME} ${TEST_LIBS}) @@ -69,9 +70,9 @@ macro(add_unit_test) set(TEST_OUTPUT ${TEST_OUTPUT} CACHE STRING "The output to generate when running the QTest unit tests") if(KDE4_TEST_OUTPUT STREQUAL "xml") - add_test(${_test_NAME} ${_test_NAME} -xml -o ${_test_NAME}.tml) + add_test(${_test_NAME} ${_test_LAUNCHER} ${_test_NAME} -xml -o ${_test_NAME}.tml) else(KDE4_TEST_OUTPUT STREQUAL "xml") - add_test(${_test_NAME} ${_test_NAME}) + add_test(${_test_NAME} ${_test_LAUNCHER} ${_test_NAME}) endif(KDE4_TEST_OUTPUT STREQUAL "xml") if(NOT MSVC_IDE) #not needed for the ide diff --git a/tests/data/dbus/interfaces/org.freedesktop.Secret.Collection.xml b/tests/data/dbus/interfaces/org.freedesktop.Secret.Collection.xml new file mode 100644 index 000000000..3b5dd64fd --- /dev/null +++ b/tests/data/dbus/interfaces/org.freedesktop.Secret.Collection.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml b/tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml new file mode 100644 index 000000000..d9c39a2e9 --- /dev/null +++ b/tests/data/dbus/interfaces/org.freedesktop.Secret.Item.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml b/tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml new file mode 100644 index 000000000..92aa8df84 --- /dev/null +++ b/tests/data/dbus/interfaces/org.freedesktop.Secret.Prompt.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/tests/data/dbus/interfaces/org.freedesktop.Secret.Service.xml b/tests/data/dbus/interfaces/org.freedesktop.Secret.Service.xml new file mode 100644 index 000000000..40240bb43 --- /dev/null +++ b/tests/data/dbus/interfaces/org.freedesktop.Secret.Service.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/data/dbus/interfaces/org.freedesktop.Secret.Session.xml b/tests/data/dbus/interfaces/org.freedesktop.Secret.Session.xml new file mode 100644 index 000000000..7d358df7b --- /dev/null +++ b/tests/data/dbus/interfaces/org.freedesktop.Secret.Session.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/data/dbus/session.conf b/tests/data/dbus/session.conf new file mode 100644 index 000000000..096da6d4c --- /dev/null +++ b/tests/data/dbus/session.conf @@ -0,0 +1,39 @@ + + + session + + unix:tmpdir=/tmp + EXTERNAL + + + + + + + /etc/dbus-1/session.conf + session.d + /etc/dbus-1/session.d + /etc/dbus-1/session-local.conf + contexts/dbus_contexts + 1000000000 + 250000000 + 1000000000 + 250000000 + 1000000000 + 240000 + 150000 + 100000 + 10000 + 100000 + 10000 + 50000 + 50000 + 50000 + + 500 + diff --git a/tests/gui/CMakeLists.txt b/tests/gui/CMakeLists.txt index 6a8d21c4a..1d5822d20 100644 --- a/tests/gui/CMakeLists.txt +++ b/tests/gui/CMakeLists.txt @@ -21,3 +21,12 @@ add_unit_test(NAME testguipixmaps SOURCES TestGuiPixmaps.cpp LIBS ${TEST_LIBRARI if(WITH_XC_BROWSER) add_unit_test(NAME testguibrowser SOURCES TestGuiBrowser.cpp ../util/TemporaryFile.cpp LIBS ${TEST_LIBRARIES}) endif() + +if(WITH_XC_FDOSECRETS) + add_unit_test(NAME testguifdosecrets + SOURCES TestGuiFdoSecrets.cpp ../util/TemporaryFile.cpp + LIBS ${TEST_LIBRARIES} + # The following doesn't work because dbus-run-session expects execname to be in PATH + # dbus-run-session -- execname + LAUNCHER dbus-run-session --config-file ${CMAKE_CURRENT_SOURCE_DIR}/../data/dbus/session.conf -- sh -c "exec ./$0") +endif() diff --git a/tests/gui/TestGuiFdoSecrets.cpp b/tests/gui/TestGuiFdoSecrets.cpp new file mode 100644 index 000000000..d223972c2 --- /dev/null +++ b/tests/gui/TestGuiFdoSecrets.cpp @@ -0,0 +1,1175 @@ +/* + * Copyright (C) 2019 Aetf + * + * 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 . + */ + +#include "TestGuiFdoSecrets.h" + +#include "fdosecrets/FdoSecretsPlugin.h" +#include "fdosecrets/FdoSecretsSettings.h" +#include "fdosecrets/objects/Collection.h" +#include "fdosecrets/objects/Item.h" +#include "fdosecrets/objects/Prompt.h" +#include "fdosecrets/objects/Service.h" +#include "fdosecrets/objects/Session.h" +#include "fdosecrets/objects/SessionCipher.h" + +#include "TestGlobal.h" +#include "config-keepassx-tests.h" + +#include "core/Bootstrap.h" +#include "core/Config.h" +#include "core/Tools.h" +#include "crypto/Crypto.h" +#include "gui/DatabaseTabWidget.h" +#include "gui/DatabaseWidget.h" +#include "gui/FileDialog.h" +#include "gui/MessageBox.h" +#include "gui/wizard/NewDatabaseWizard.h" +#include "util/TemporaryFile.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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); + 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" + +#define VERIFY(statement) \ + do { \ + if (!QTest::qVerify(static_cast(statement), #statement, "", __FILE__, __LINE__)) \ + return {}; \ + } while (false) + +#define COMPARE(actual, expected) \ + do { \ + if (!QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)) \ + return {}; \ + } while (false) + +#define FAIL(message) \ + do { \ + QTest::qFail(static_cast(message), __FILE__, __LINE__); \ + return {}; \ + } while (false) + +#define COMPARE_DBUS_LOCAL_CALL(actual, expected) \ + do { \ + const auto a = (actual); \ + QVERIFY(!a.isError()); \ + QCOMPARE(a.value(), (expected)); \ + } while (false) + +#define CHECKED_DBUS_LOCAL_CALL(name, stmt) \ + std::remove_cv::type name; \ + do { \ + const auto rep = stmt; \ + QVERIFY(!rep.isError()); \ + name = rep.value(); \ + } while (false) + +namespace +{ + std::unique_ptr interfaceOf(const QDBusObjectPath& objPath, const QString& interface) + { + std::unique_ptr iface(new QDBusInterface(DBUS_SERVICE_SECRET, objPath.path(), interface)); + iface->setTimeout(5); + VERIFY(iface->isValid()); + return iface; + } + + std::unique_ptr interfaceOf(FdoSecrets::DBusObject* obj) + { + VERIFY(obj); + auto metaAdaptor = obj->dbusAdaptor().metaObject(); + auto ifaceName = metaAdaptor->classInfo(metaAdaptor->indexOfClassInfo("D-Bus Interface")).value(); + + return interfaceOf(obj->objectPath(), ifaceName); + } + + template QString extractElement(const QString& doc, T cond) + { + QXmlStreamReader reader(doc); + while (!reader.atEnd()) { + int st = reader.characterOffset(); + + if (reader.readNext() != QXmlStreamReader::StartElement || !cond(reader)) { + continue; + } + + reader.skipCurrentElement(); + if (reader.hasError()) { + break; + } + + // remove whitespaces between elements to be a little bit flexible + int ed = reader.characterOffset(); + return doc.mid(st - 1, ed - st + 1).replace(QRegularExpression(R"(>[\s\n]+<)"), "><"); + } + VERIFY(!reader.hasError()); + return {}; + } + + bool checkDBusSpec(const QString& path, const QString& interface) + { + QFile f(QStringLiteral(KEEPASSX_TEST_DATA_DIR "/dbus/interfaces/%1.xml").arg(interface)); + VERIFY(f.open(QFile::ReadOnly | QFile::Text)); + QTextStream in(&f); + auto spec = in.readAll().replace(QRegularExpression(R"(>[\s\n]+<)"), "><").trimmed(); + + auto bus = QDBusConnection::sessionBus(); + auto msg = QDBusMessage::createMethodCall( + DBUS_SERVICE_SECRET, path, "org.freedesktop.DBus.Introspectable", "Introspect"); + + // BlockWithGui enters event loop + auto reply = QDBusPendingReply(bus.call(msg, QDBus::BlockWithGui, 5)); + VERIFY(reply.isValid()); + auto actual = extractElement(reply.argumentAt<0>(), [&](const QXmlStreamReader& reader) { + return reader.name() == "interface" && reader.attributes().value("name") == interface; + }); + + COMPARE(actual, spec); + return true; + } +} // namespace + +using namespace FdoSecrets; + +// for use in QSignalSpy +Q_DECLARE_METATYPE(Collection*); +Q_DECLARE_METATYPE(Item*); + +TestGuiFdoSecrets::~TestGuiFdoSecrets() = default; + +void TestGuiFdoSecrets::initTestCase() +{ + // for use in QSignalSpy + qRegisterMetaType(); + qRegisterMetaType(); + + QVERIFY(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 secret service integration (activate within individual tests to test the plugin) + FdoSecrets::settings()->setEnabled(false); + // activate within individual tests + FdoSecrets::settings()->setShowNotification(false); + + Bootstrap::bootstrapApplication(); + + m_mainWindow.reset(new MainWindow()); + m_tabWidget = m_mainWindow->findChild("tabWidget"); + QVERIFY(m_tabWidget); + m_plugin = m_mainWindow->findChild(); + QVERIFY(m_plugin); + m_mainWindow->show(); + + // Load the NewDatabase.kdbx file into temporary storage + QFile sourceDbFile(QStringLiteral(KEEPASSX_TEST_DATA_DIR "/NewDatabase.kdbx")); + QVERIFY(sourceDbFile.open(QIODevice::ReadOnly)); + QVERIFY(Tools::readAllFromDevice(&sourceDbFile, m_dbData)); + sourceDbFile.close(); + + // set keys for session encryption + m_serverPublic = MpiFromHex("e407997e8b918419cf851cf3345358fdf" + "ffb9564a220ac9c3934efd277cea20d17" + "467ecdc56e817f75ac39501f38a4a04ff" + "64d627e16c09981c7ad876da255b61c8e" + "6a8408236c2a4523cfe6961c26dbdfc77" + "c1a27a5b425ca71a019e829fae32c0b42" + "0e1b3096b48bc2ce9ccab1d1ff13a5eb4" + "b263cee30bdb1a57af9bfa93f"); + m_serverPrivate = MpiFromHex("013f4f3381ef0ca11c4c7363079577b56" + "99b238644e0aba47e24bdba6173590216" + "4f1e12dd0944800a373e090e63192f53b" + "93583e9a9e50bb9d792aafaa3a0f5ae77" + "de0c3423f5820848d88ee3bdd01c889f2" + "7af58a02f5b6693d422b9d189b300d7b1" + "be5076b5795cf8808c31e2e2898368d18" + "ab5c26b0ea3480c9aba8154cf"); + // use the same cipher to do the client side encryption, but exchange the position of client/server keys + m_cipher.reset(new DhIetf1024Sha256Aes128CbcPkcs7); + QVERIFY(m_cipher->initialize(MpiFromBytes(MpiToBytes(m_serverPublic)), + MpiFromHex("30d18c6b328bac970c05bda6af2e708b9" + "d6bbbb6dc136c1a2d96e870fabc86ad74" + "1846a26a4197f32f65ea2e7580ad2afe3" + "dd5d6c1224b8368b0df2cd75d520a9ff9" + "7fe894cc7da71b7bd285b4633359c16c8" + "d341f822fa4f0fdf59b5d3448658c46a2" + "a86dbb14ff85823873f8a259ccc52bbb8" + "2b5a4c2a75447982553b42221"), + MpiFromHex("84aafe9c9f356f7762307f4d791acb59e" + "8e3fd562abdbb481d0587f8400ad6c51d" + "af561a1beb9a22c8cd4d2807367c5787b" + "2e06d631ccbb5194b6bb32211583ce688" + "f9c2cebc22a9e4d494d12ebdd570c61a1" + "62a94e88561d25ccd0415339d1f59e1b0" + "6bc6b6b5fde46e23b2410eb034be390d3" + "2407ec7ae90f0831f24afd5ac"))); +} + +// 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 + QVERIFY(m_dbFile->open()); + QCOMPARE(m_dbFile->write(m_dbData), static_cast((m_dbData.size()))); + m_dbFile->close(); + + // make sure window is activated or focus tests may fail + m_mainWindow->activateWindow(); + QApplication::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 expsoe the root group + FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); + QVERIFY(m_dbWidget->save()); +} + +// Every test ends with closing the temp database without saving +void TestGuiFdoSecrets::cleanup() +{ + // restore to default settings + FdoSecrets::settings()->setShowNotification(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(); + } + QVERIFY(m_tabWidget->closeAllDatabaseTabs()); + QApplication::processEvents(); + + if (m_dbFile) { + m_dbFile->remove(); + } +} + +void TestGuiFdoSecrets::cleanupTestCase() +{ + if (m_dbFile) { + m_dbFile->remove(); + } +} + +void TestGuiFdoSecrets::testDBusSpec() +{ + auto service = enableService(); + QVERIFY(service); + + // service + QCOMPARE(service->objectPath().path(), QStringLiteral(DBUS_PATH_SECRETS)); + QVERIFY(checkDBusSpec(service->objectPath().path(), DBUS_INTERFACE_SECRET_SERVICE)); + + // default alias + QVERIFY(checkDBusSpec(DBUS_PATH_DEFAULT_ALIAS, DBUS_INTERFACE_SECRET_COLLECTION)); + + // collection + auto coll = getDefaultCollection(service); + QVERIFY(coll); + QVERIFY(checkDBusSpec(coll->objectPath().path(), DBUS_INTERFACE_SECRET_COLLECTION)); + + // item + auto item = getFirstItem(coll); + QVERIFY(item); + QVERIFY(checkDBusSpec(item->objectPath().path(), DBUS_INTERFACE_SECRET_ITEM)); + + // session + auto sess = openSession(service, PlainCipher::Algorithm); + QVERIFY(sess); + QVERIFY(checkDBusSpec(sess->objectPath().path(), DBUS_INTERFACE_SECRET_SESSION)); + + // prompt + FdoSecrets::settings()->setNoConfirmDeleteItem(true); + PromptBase* prompt = nullptr; + { + auto rep = item->deleteItem(); + QVERIFY(!rep.isError()); + prompt = rep.value(); + } + QVERIFY(prompt); + QVERIFY(checkDBusSpec(prompt->objectPath().path(), DBUS_INTERFACE_SECRET_PROMPT)); +} + +void TestGuiFdoSecrets::testServiceEnable() +{ + QSignalSpy sigError(m_plugin, SIGNAL(error(QString))); + QVERIFY(sigError.isValid()); + + QSignalSpy sigStarted(m_plugin, SIGNAL(secretServiceStarted())); + QVERIFY(sigStarted.isValid()); + + // make sure no one else is holding the service + QVERIFY(!QDBusConnection::sessionBus().interface()->isServiceRegistered(DBUS_SERVICE_SECRET)); + + // enable the service + auto service = enableService(); + QVERIFY(service); + + // service started without error + QVERIFY(sigError.isEmpty()); + QCOMPARE(sigStarted.size(), 1); + + QApplication::processEvents(); + + QVERIFY(QDBusConnection::sessionBus().interface()->isServiceRegistered(DBUS_SERVICE_SECRET)); + + // there will be one default collection + auto coll = getDefaultCollection(service); + QVERIFY(coll); + + COMPARE_DBUS_LOCAL_CALL(coll->locked(), false); + COMPARE_DBUS_LOCAL_CALL(coll->label(), m_db->metadata()->name()); + COMPARE_DBUS_LOCAL_CALL( + coll->created(), + static_cast(m_db->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000)); + COMPARE_DBUS_LOCAL_CALL( + coll->modified(), + static_cast(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(); + QVERIFY(service); + + // no collections + COMPARE_DBUS_LOCAL_CALL(service->collections(), QList{}); +} + +void TestGuiFdoSecrets::testServiceSearch() +{ + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + auto item = getFirstItem(coll); + QVERIFY(item); + + item->backend()->attributes()->set("fdosecrets-test", "1"); + item->backend()->attributes()->set("fdosecrets-test-protected", "2", true); + const QString crazyKey = "_a:bc&-+'-e%12df_d"; + const QString crazyValue = "[v]al@-ue"; + item->backend()->attributes()->set(crazyKey, crazyValue); + + // search by title + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"Title", item->backend()->title()}}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked, {item}); + } + + // search by attribute + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"fdosecrets-test", "1"}}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked, {item}); + } + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{crazyKey, crazyValue}}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked, {item}); + } + + // searching using empty terms returns nothing + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked.size(), 0); + } + + // searching using protected attributes or password returns nothing + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"Password", item->backend()->password()}}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked.size(), 0); + } + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"fdosecrets-test-protected", "2"}}, locked)); + QCOMPARE(locked.size(), 0); + QCOMPARE(unlocked.size(), 0); + } +} + +void TestGuiFdoSecrets::testServiceUnlock() +{ + lockDatabaseInBackend(); + + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + + QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); + QVERIFY(spyCollectionCreated.isValid()); + QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); + QVERIFY(spyCollectionDeleted.isValid()); + QSignalSpy spyCollectionChanged(service, SIGNAL(collectionChanged(Collection*))); + QVERIFY(spyCollectionChanged.isValid()); + + PromptBase* prompt = nullptr; + { + CHECKED_DBUS_LOCAL_CALL(unlocked, service->unlock({coll.data()}, prompt)); + // nothing is unlocked immediately without user's action + QVERIFY(unlocked.isEmpty()); + } + QVERIFY(prompt); + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + // nothing is unlocked yet + QCOMPARE(spyPromptCompleted.count(), 0); + QVERIFY(coll); + QVERIFY(coll->backend()->isLocked()); + + // drive the prompt + QVERIFY(!prompt->prompt("").isError()); + + // still not unlocked before user action + QCOMPARE(spyPromptCompleted.count(), 0); + QVERIFY(coll); + QVERIFY(coll->backend()->isLocked()); + + // interact with the dialog + QApplication::processEvents(); + { + auto dbOpenDlg = m_tabWidget->findChild(); + QVERIFY(dbOpenDlg); + auto editPassword = dbOpenDlg->findChild("editPassword"); + QVERIFY(editPassword); + editPassword->setFocus(); + QTest::keyClicks(editPassword, "a"); + QTest::keyClick(editPassword, Qt::Key_Enter); + } + QApplication::processEvents(); + + // unlocked + QVERIFY(coll); + QVERIFY(!coll->backend()->isLocked()); + + QCOMPARE(spyPromptCompleted.count(), 1); + { + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.size(), 2); + QCOMPARE(args.at(0).toBool(), false); + QCOMPARE(args.at(1).value>(), {coll->objectPath()}); + } + QCOMPARE(spyCollectionCreated.count(), 0); + QCOMPARE(spyCollectionChanged.count(), 1); + { + auto args = spyCollectionChanged.takeFirst(); + QCOMPARE(args.size(), 1); + QCOMPARE(args.at(0).value(), coll.data()); + } + QCOMPARE(spyCollectionDeleted.count(), 0); +} + +void TestGuiFdoSecrets::testServiceLock() +{ + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + + QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); + QVERIFY(spyCollectionCreated.isValid()); + QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); + QVERIFY(spyCollectionDeleted.isValid()); + QSignalSpy spyCollectionChanged(service, SIGNAL(collectionChanged(Collection*))); + QVERIFY(spyCollectionChanged.isValid()); + + // if the db is modified, prompt user + m_db->markAsModified(); + { + PromptBase* prompt = nullptr; + CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt)); + QCOMPARE(locked.size(), 0); + QVERIFY(prompt); + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + // prompt and click cancel + MessageBox::setNextAnswer(MessageBox::Cancel); + QVERIFY(!prompt->prompt("").isError()); + QApplication::processEvents(); + + QVERIFY(!coll->backend()->isLocked()); + + QCOMPARE(spyPromptCompleted.count(), 1); + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.count(), 2); + QCOMPARE(args.at(0).toBool(), true); + QCOMPARE(args.at(1).value>(), {}); + } + { + PromptBase* prompt = nullptr; + CHECKED_DBUS_LOCAL_CALL(locked, service->lock({coll}, prompt)); + QCOMPARE(locked.size(), 0); + QVERIFY(prompt); + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + // prompt and click save + MessageBox::setNextAnswer(MessageBox::Save); + QVERIFY(!prompt->prompt("").isError()); + QApplication::processEvents(); + + QVERIFY(coll->backend()->isLocked()); + + QCOMPARE(spyPromptCompleted.count(), 1); + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.count(), 2); + QCOMPARE(args.at(0).toBool(), false); + QCOMPARE(args.at(1).value>(), {coll->objectPath()}); + } + + QCOMPARE(spyCollectionCreated.count(), 0); + QCOMPARE(spyCollectionChanged.count(), 1); + { + auto args = spyCollectionChanged.takeFirst(); + QCOMPARE(args.size(), 1); + QCOMPARE(args.at(0).value(), coll.data()); + } + QCOMPARE(spyCollectionDeleted.count(), 0); + + // locking item locks the whole db + unlockDatabaseInBackend(); + { + auto item = getFirstItem(coll); + PromptBase* prompt = nullptr; + CHECKED_DBUS_LOCAL_CALL(locked, service->lock({item}, prompt)); + QCOMPARE(locked.size(), 0); + QVERIFY(prompt); + + MessageBox::setNextAnswer(MessageBox::Save); + QVERIFY(!prompt->prompt("").isError()); + QApplication::processEvents(); + + QVERIFY(coll->backend()->isLocked()); + } +} + +void TestGuiFdoSecrets::testSessionOpen() +{ + auto service = enableService(); + QVERIFY(service); + + auto sess = openSession(service, PlainCipher::Algorithm); + QVERIFY(sess); + QCOMPARE(service->sessions().size(), 1); + + sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm); + QVERIFY(sess); + QCOMPARE(service->sessions().size(), 2); +} + +void TestGuiFdoSecrets::testSessionClose() +{ + auto service = enableService(); + QVERIFY(service); + + auto sess = openSession(service, PlainCipher::Algorithm); + QVERIFY(sess); + + QCOMPARE(service->sessions().size(), 1); + + auto rep = sess->close(); + QVERIFY(!rep.isError()); + + QCOMPARE(service->sessions().size(), 0); +} + +void TestGuiFdoSecrets::testCollectionCreate() +{ + auto service = enableService(); + QVERIFY(service); + + QSignalSpy spyCollectionCreated(service, SIGNAL(collectionCreated(Collection*))); + QVERIFY(spyCollectionCreated.isValid()); + + // returns existing if alias is nonempty and exists + { + PromptBase* prompt = nullptr; + CHECKED_DBUS_LOCAL_CALL( + coll, service->createCollection({{DBUS_INTERFACE_SECRET_COLLECTION ".Label", "NewDB"}}, "default", prompt)); + QVERIFY(!prompt); + QCOMPARE(coll, getDefaultCollection(service).data()); + } + QCOMPARE(spyCollectionCreated.count(), 0); + + // create new one and set properties + { + PromptBase* prompt = nullptr; + CHECKED_DBUS_LOCAL_CALL( + created, + service->createCollection({{DBUS_INTERFACE_SECRET_COLLECTION ".Label", "Test NewDB"}}, "mydatadb", prompt)); + QVERIFY(!created); + QVERIFY(prompt); + + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + QTimer::singleShot(50, this, SLOT(createDatabaseCallback())); + QVERIFY(!prompt->prompt("").isError()); + QApplication::processEvents(); + + QCOMPARE(spyPromptCompleted.count(), 1); + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.size(), 2); + QCOMPARE(args.at(0).toBool(), false); + auto coll = FdoSecrets::pathToObject(args.at(1).value()); + QVERIFY(coll); + + QCOMPARE(coll->backend()->database()->metadata()->name(), QStringLiteral("Test NewDB")); + + QCOMPARE(spyCollectionCreated.count(), 1); + { + auto args = spyCollectionCreated.takeFirst(); + QCOMPARE(args.size(), 1); + QCOMPARE(args.at(0).value(), coll); + } + } +} + +void TestGuiFdoSecrets::createDatabaseCallback() +{ + auto wizard = m_tabWidget->findChild(); + QVERIFY(wizard); + + QCOMPARE(wizard->currentId(), 0); + wizard->next(); + wizard->next(); + QCOMPARE(wizard->currentId(), 2); + + // enter password + auto* passwordEdit = wizard->findChild("enterPasswordEdit"); + auto* passwordRepeatEdit = wizard->findChild("repeatPasswordEdit"); + QTest::keyClicks(passwordEdit, "test"); + QTest::keyClick(passwordEdit, Qt::Key::Key_Tab); + QTest::keyClicks(passwordRepeatEdit, "test"); + + // save database to temporary file + TemporaryFile tmpFile; + QVERIFY(tmpFile.open()); + tmpFile.close(); + fileDialog()->setNextFileName(tmpFile.fileName()); + + wizard->accept(); + + tmpFile.remove(); +} + +void TestGuiFdoSecrets::testCollectionDelete() +{ + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + // closing the tab calls coll->deleteLater() + // but deleteLater is not processed in QApplication::processEvent + // see https://doc.qt.io/qt-5/qcoreapplication.html#processEvents + auto rawColl = coll.data(); + + QSignalSpy spyCollectionDeleted(service, SIGNAL(collectionDeleted(Collection*))); + QVERIFY(spyCollectionDeleted.isValid()); + + m_db->markAsModified(); + CHECKED_DBUS_LOCAL_CALL(prompt, coll->deleteCollection()); + QVERIFY(prompt); + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + // prompt and click save + MessageBox::setNextAnswer(MessageBox::Save); + QVERIFY(!prompt->prompt("").isError()); + + QApplication::processEvents(); + + // closing the tab should have deleted coll if not in testing + // but deleteLater is not processed in QApplication::processEvent + // see https://doc.qt.io/qt-5/qcoreapplication.html#processEvents + // QVERIFY(!coll); + + QCOMPARE(spyPromptCompleted.count(), 1); + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.count(), 2); + QCOMPARE(args.at(0).toBool(), false); + QCOMPARE(args.at(1).toString(), QStringLiteral("")); + + QCOMPARE(spyCollectionDeleted.count(), 1); + { + auto args = spyCollectionDeleted.takeFirst(); + QCOMPARE(args.size(), 1); + QCOMPARE(args.at(0).value(), rawColl); + } +} + +void TestGuiFdoSecrets::testItemCreate() +{ + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm); + QVERIFY(sess); + + // create item + StringStringMap attributes{ + {"application", "fdosecrets-test"}, + {"attr-i[bute]", "![some] -value*"}, + }; + + auto item = createItem(sess, coll, "abc", "Password", attributes, false); + QVERIFY(item); + + // attributes + { + CHECKED_DBUS_LOCAL_CALL(actual, item->attributes()); + for (const auto& key : attributes.keys()) { + QVERIFY(actual.contains(key)); + QCOMPARE(actual[key], attributes[key]); + } + } + + // label + COMPARE_DBUS_LOCAL_CALL(item->label(), QStringLiteral("abc")); + + // secrets + { + CHECKED_DBUS_LOCAL_CALL(ss, item->getSecret(sess)); + ss = m_cipher->decrypt(ss); + QCOMPARE(ss.value, QByteArray("Password")); + } + + // searchable + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems(attributes, locked)); + QCOMPARE(locked, QList{}); + QCOMPARE(unlocked, QList{item}); + } + { + CHECKED_DBUS_LOCAL_CALL(unlocked, coll->searchItems(attributes)); + QVERIFY(unlocked.contains(item)); + } +} + +void TestGuiFdoSecrets::testItemReplace() +{ + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm); + QVERIFY(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); + QVERIFY(item1); + auto item2 = createItem(sess, coll, "abc2", "Password", attr2, false); + QVERIFY(item2); + + { + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked)); + QCOMPARE(unlocked.size(), 2); + } + + { + // when replace, existing item with matching attr is updated + auto item3 = createItem(sess, coll, "abc3", "Password", attr2, true); + QVERIFY(item3); + QCOMPARE(item2, item3); + COMPARE_DBUS_LOCAL_CALL(item3->label(), QStringLiteral("abc3")); + // there is still 2 entries + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked)); + QCOMPARE(unlocked.size(), 2); + } + + { + // when NOT replace, another entry is created + auto item4 = createItem(sess, coll, "abc4", "Password", attr2, false); + QVERIFY(item4); + COMPARE_DBUS_LOCAL_CALL(item2->label(), QStringLiteral("abc3")); + COMPARE_DBUS_LOCAL_CALL(item4->label(), QStringLiteral("abc4")); + // there is 3 entries + QList locked; + CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"application", "fdosecrets-test"}}, locked)); + QCOMPARE(unlocked.size(), 3); + } +} + +void TestGuiFdoSecrets::testItemSecret() +{ + const QString TEXT_PLAIN = "text/plain"; + const QString APPLICATION_OCTET_STREAM = "application/octet-stream"; + + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + auto item = getFirstItem(coll); + QVERIFY(item); + auto sess = openSession(service, DhIetf1024Sha256Aes128CbcPkcs7::Algorithm); + QVERIFY(sess); + + // plain text secret + { + CHECKED_DBUS_LOCAL_CALL(encrypted, item->getSecret(sess)); + auto ss = m_cipher->decrypt(encrypted); + QCOMPARE(ss.contentType, TEXT_PLAIN); + QCOMPARE(ss.value, item->backend()->password().toUtf8()); + } + + // get secret with notification (only works when called from DBUS) + FdoSecrets::settings()->setShowNotification(true); + { + QSignalSpy spyShowNotification(m_plugin, SIGNAL(requestShowNotification(QString, QString, int))); + QVERIFY(spyShowNotification.isValid()); + + auto iitem = interfaceOf(item); + QVERIFY(static_cast(iitem)); + + auto replyMsg = iitem->call(QDBus::BlockWithGui, "GetSecret", QVariant::fromValue(sess->objectPath())); + auto reply = QDBusPendingReply(replyMsg); + QVERIFY(reply.isValid()); + auto ss = m_cipher->decrypt(reply.argumentAt<0>()); + + QCOMPARE(ss.contentType, TEXT_PLAIN); + QCOMPARE(ss.value, item->backend()->password().toUtf8()); + + QCOMPARE(spyShowNotification.count(), 1); + } + FdoSecrets::settings()->setShowNotification(false); + + // set secret with plain text + { + SecretStruct ss; + ss.contentType = TEXT_PLAIN; + ss.value = "NewPassword"; + ss.session = sess->objectPath(); + QVERIFY(!item->setSecret(m_cipher->encrypt(ss)).isError()); + + QCOMPARE(item->backend()->password().toUtf8(), ss.value); + } + + // set secret with something else is saved as attachment + { + SecretStruct expected; + expected.contentType = APPLICATION_OCTET_STREAM; + expected.value = "NewPasswordBinary"; + expected.session = sess->objectPath(); + QVERIFY(!item->setSecret(m_cipher->encrypt(expected)).isError()); + + QCOMPARE(item->backend()->password(), QStringLiteral("")); + + CHECKED_DBUS_LOCAL_CALL(encrypted, item->getSecret(sess)); + auto ss = m_cipher->decrypt(encrypted); + QCOMPARE(ss.contentType, expected.contentType); + QCOMPARE(ss.value, expected.value); + } +} + +void TestGuiFdoSecrets::testItemDelete() +{ + FdoSecrets::settings()->setNoConfirmDeleteItem(false); + + auto service = enableService(); + QVERIFY(service); + auto coll = getDefaultCollection(service); + QVERIFY(coll); + auto item = getFirstItem(coll); + QVERIFY(item); + auto rawItem = item.data(); + + QSignalSpy spyItemDeleted(coll, SIGNAL(itemDeleted(Item*))); + QVERIFY(spyItemDeleted.isValid()); + + CHECKED_DBUS_LOCAL_CALL(prompt, item->deleteItem()); + QVERIFY(prompt); + + QSignalSpy spyPromptCompleted(prompt, SIGNAL(completed(bool, QVariant))); + QVERIFY(spyPromptCompleted.isValid()); + + // prompt and click save + if (item->isDeletePermanent()) { + MessageBox::setNextAnswer(MessageBox::Delete); + } else { + MessageBox::setNextAnswer(MessageBox::Move); + } + QVERIFY(!prompt->prompt("").isError()); + + QApplication::processEvents(); + + QCOMPARE(spyPromptCompleted.count(), 1); + auto args = spyPromptCompleted.takeFirst(); + QCOMPARE(args.count(), 2); + QCOMPARE(args.at(0).toBool(), false); + QCOMPARE(args.at(1).toString(), QStringLiteral("")); + + QCOMPARE(spyItemDeleted.count(), 1); + { + auto args = spyItemDeleted.takeFirst(); + QCOMPARE(args.size(), 1); + QCOMPARE(args.at(0).value(), rawItem); + } +} + +void TestGuiFdoSecrets::testAlias() +{ + auto service = enableService(); + QVERIFY(service); + + // read default alias + CHECKED_DBUS_LOCAL_CALL(coll, service->readAlias("default")); + QVERIFY(coll); + + // set extra alias + QVERIFY(!service->setAlias("another", coll).isError()); + + // get using extra alias + CHECKED_DBUS_LOCAL_CALL(coll2, service->readAlias("another")); + QVERIFY(coll2); + QCOMPARE(coll, coll2); +} + +void TestGuiFdoSecrets::testDefaultAliasAlwaysPresent() +{ + auto service = enableService(); + QVERIFY(service); + + // one collection, which is default alias + auto coll = getDefaultCollection(service); + QVERIFY(coll); + + // after locking, the collection is still there, but locked + lockDatabaseInBackend(); + + coll = getDefaultCollection(service); + QVERIFY(coll); + COMPARE_DBUS_LOCAL_CALL(coll->locked(), true); + + // unlock the database, the alias and collection is present + unlockDatabaseInBackend(); + + coll = getDefaultCollection(service); + QVERIFY(coll); + COMPARE_DBUS_LOCAL_CALL(coll->locked(), false); +} + +void TestGuiFdoSecrets::testExposeSubgroup() +{ + auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking/Subgroup"); + QVERIFY(subgroup); + FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid()); + auto service = enableService(); + QVERIFY(service); + + auto coll = getDefaultCollection(service); + QVERIFY(coll); + + // exposing subgroup does not expose entries in other groups + auto items = coll->items(); + QVERIFY(!items.isError()); + QList exposedEntries; + for (const auto& item : items.value()) { + exposedEntries << item->backend(); + } + QCOMPARE(exposedEntries, subgroup->entries()); +} + +void TestGuiFdoSecrets::testModifiyingExposedGroup() +{ + // test when exposed group is removed the collection is not exposed anymore + auto subgroup = m_db->rootGroup()->findGroupByPath("/Homebanking"); + QVERIFY(subgroup); + FdoSecrets::settings()->setExposedGroup(m_db, subgroup->uuid()); + auto service = enableService(); + QVERIFY(service); + + { + CHECKED_DBUS_LOCAL_CALL(colls, service->collections()); + QCOMPARE(colls.size(), 1); + } + + m_db->metadata()->setRecycleBinEnabled(true); + m_db->recycleGroup(subgroup); + QApplication::processEvents(); + + { + CHECKED_DBUS_LOCAL_CALL(colls, service->collections()); + QCOMPARE(colls.size(), 0); + } + + // test setting another exposed group, the collection will be exposed again + FdoSecrets::settings()->setExposedGroup(m_db, m_db->rootGroup()->uuid()); + QApplication::processEvents(); + { + CHECKED_DBUS_LOCAL_CALL(colls, service->collections()); + QCOMPARE(colls.size(), 1); + } +} + +QPointer TestGuiFdoSecrets::enableService() +{ + FdoSecrets::settings()->setEnabled(true); + VERIFY(m_plugin); + m_plugin->updateServiceState(); + return m_plugin->serviceInstance(); +} + +QPointer TestGuiFdoSecrets::openSession(Service* service, const QString& algo) +{ + // open session has to be called actually over DBUS to get peer info + + VERIFY(service); + auto iservice = interfaceOf(service); + VERIFY(iservice); + + if (algo == PlainCipher::Algorithm) { + auto replyMsg = iservice->call(QDBus::BlockWithGui, "OpenSession", algo, QVariant::fromValue(QDBusVariant(""))); + auto reply = QDBusPendingReply(replyMsg); + + VERIFY(reply.isValid()); + return FdoSecrets::pathToObject(reply.argumentAt<1>()); + } else if (algo == DhIetf1024Sha256Aes128CbcPkcs7::Algorithm) { + + DhIetf1024Sha256Aes128CbcPkcs7::fixNextServerKeys(MpiFromBytes(MpiToBytes(m_serverPrivate)), + MpiFromBytes(MpiToBytes(m_serverPublic))); + + auto replyMsg = iservice->call( + QDBus::BlockWithGui, "OpenSession", algo, QVariant::fromValue(QDBusVariant(m_cipher->m_publicKey))); + auto reply = QDBusPendingReply(replyMsg); + VERIFY(reply.isValid()); + COMPARE(qvariant_cast(reply.argumentAt<0>().variant()), MpiToBytes(m_serverPublic)); + return FdoSecrets::pathToObject(reply.argumentAt<1>()); + } + FAIL("Unsupported algorithm"); +} + +QPointer TestGuiFdoSecrets::getDefaultCollection(Service* service) +{ + VERIFY(service); + auto coll = service->readAlias("default"); + VERIFY(!coll.isError()); + return coll.value(); +} + +QPointer TestGuiFdoSecrets::getFirstItem(Collection* coll) +{ + VERIFY(coll); + auto items = coll->items(); + VERIFY(!items.isError()); + VERIFY(!items.value().isEmpty()); + return items.value().at(0); +} + +QPointer TestGuiFdoSecrets::createItem(Session* sess, + Collection* coll, + const QString& label, + const QString& pass, + const StringStringMap& attr, + bool replace) +{ + VERIFY(sess); + VERIFY(coll); + + QVariantMap properties{ + {DBUS_INTERFACE_SECRET_ITEM ".Label", QVariant::fromValue(label)}, + {DBUS_INTERFACE_SECRET_ITEM ".Attributes", QVariant::fromValue(attr)}, + }; + + SecretStruct ss; + ss.session = sess->objectPath(); + ss.value = pass.toLocal8Bit(); + ss.contentType = "plain/text"; + ss = m_cipher->encrypt(ss); + + PromptBase* prompt = nullptr; + auto item = coll->createItem(properties, ss, replace, prompt); + VERIFY(!item.isError()); + // creating item does not have a prompt to show + VERIFY(!prompt); + return item.value(); +} + +void TestGuiFdoSecrets::lockDatabaseInBackend() +{ + m_dbWidget->lock(); + m_db.reset(); + QApplication::processEvents(); +} + +void TestGuiFdoSecrets::unlockDatabaseInBackend() +{ + m_dbWidget->performUnlockDatabase("a"); + m_db = m_dbWidget->database(); + QApplication::processEvents(); +} diff --git a/tests/gui/TestGuiFdoSecrets.h b/tests/gui/TestGuiFdoSecrets.h new file mode 100644 index 000000000..8a961eb8e --- /dev/null +++ b/tests/gui/TestGuiFdoSecrets.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2019 Aetf + * + * 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 . + */ + +#ifndef KEEPASSXC_TESTGUIFDOSECRETS_H +#define KEEPASSXC_TESTGUIFDOSECRETS_H + +#include +#include +#include +#include +#include +#include + +#include "fdosecrets/GcryptMPI.h" +#include "fdosecrets/objects/DBusTypes.h" + +class MainWindow; +class Database; +class DatabaseTabWidget; +class DatabaseWidget; +class TemporaryFile; +class FdoSecretsPlugin; +namespace FdoSecrets +{ + class Service; + class Session; + class Collection; + class Item; + class Prompt; + class DhIetf1024Sha256Aes128CbcPkcs7; +} // namespace FdoSecrets + +class QAbstractItemView; + +class TestGuiFdoSecrets : public QObject +{ + Q_OBJECT + +public: + ~TestGuiFdoSecrets() override; + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + + void testDBusSpec(); + + void testServiceEnable(); + void testServiceEnableNoExposedDatabase(); + void testServiceSearch(); + void testServiceUnlock(); + void testServiceLock(); + + void testSessionOpen(); + void testSessionClose(); + + void testCollectionCreate(); + void testCollectionDelete(); + + void testItemCreate(); + void testItemReplace(); + void testItemSecret(); + void testItemDelete(); + + void testAlias(); + void testDefaultAliasAlwaysPresent(); + + void testExposeSubgroup(); + void testModifiyingExposedGroup(); + +protected slots: + void createDatabaseCallback(); + +private: + void lockDatabaseInBackend(); + void unlockDatabaseInBackend(); + QPointer enableService(); + QPointer openSession(FdoSecrets::Service* service, const QString& algo); + QPointer getDefaultCollection(FdoSecrets::Service* service); + QPointer getFirstItem(FdoSecrets::Collection* coll); + QPointer createItem(FdoSecrets::Session* sess, + FdoSecrets::Collection* coll, + const QString& label, + const QString& pass, + const StringStringMap& attr, + bool replace); + +private: + QScopedPointer m_mainWindow; + QPointer m_tabWidget; + QPointer m_dbWidget; + QSharedPointer m_db; + + QPointer m_plugin; + + // For DH session tests + GcryptMPI m_serverPrivate; + GcryptMPI m_serverPublic; + std::unique_ptr m_cipher; + + QByteArray m_dbData; + QScopedPointer m_dbFile; +}; + +#endif // KEEPASSXC_TESTGUIFDOSECRETS_H -- cgit v1.2.3