mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
a651d7049d
- Use completeBaseName rather than baseName to ensure nonempty name - Handle two databases have the same name - Cleanup Service::onDatabaseTabOpened logic
1223 lines
42 KiB
C++
1223 lines
42 KiB
C++
/*
|
|
* 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/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/Config.h"
|
|
#include "core/Tools.h"
|
|
#include "crypto/Crypto.h"
|
|
#include "gui/Application.h"
|
|
#include "gui/DatabaseTabWidget.h"
|
|
#include "gui/DatabaseWidget.h"
|
|
#include "gui/FileDialog.h"
|
|
#include "gui/MainWindow.h"
|
|
#include "gui/MessageBox.h"
|
|
#include "gui/wizard/NewDatabaseWizard.h"
|
|
#include "util/TemporaryFile.h"
|
|
|
|
#include <QAbstractItemView>
|
|
#include <QDBusConnection>
|
|
#include <QDBusConnectionInterface>
|
|
#include <QDBusInterface>
|
|
#include <QLineEdit>
|
|
#include <QPointer>
|
|
#include <QSignalSpy>
|
|
#include <QTemporaryDir>
|
|
#include <QXmlStreamReader>
|
|
|
|
#include <memory>
|
|
#include <type_traits>
|
|
|
|
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"
|
|
|
|
#define VERIFY(statement) \
|
|
do { \
|
|
if (!QTest::qVerify(static_cast<bool>(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<const char*>(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<decltype(stmt)::value_type>::type name; \
|
|
do { \
|
|
const auto rep = stmt; \
|
|
QVERIFY(!rep.isError()); \
|
|
name = rep.value(); \
|
|
} while (false)
|
|
|
|
namespace
|
|
{
|
|
std::unique_ptr<QDBusInterface> interfaceOf(const QDBusObjectPath& objPath, const QString& interface)
|
|
{
|
|
std::unique_ptr<QDBusInterface> iface(new QDBusInterface(DBUS_SERVICE_SECRET, objPath.path(), interface));
|
|
iface->setTimeout(5);
|
|
VERIFY(iface->isValid());
|
|
return iface;
|
|
}
|
|
|
|
std::unique_ptr<QDBusInterface> 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 <typename T> 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<QString>(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<Collection*>();
|
|
qRegisterMetaType<Item*>();
|
|
|
|
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);
|
|
|
|
Application::bootstrap();
|
|
|
|
m_mainWindow.reset(new MainWindow());
|
|
m_tabWidget = m_mainWindow->findChild<DatabaseTabWidget*>("tabWidget");
|
|
QVERIFY(m_tabWidget);
|
|
m_plugin = FdoSecretsPlugin::getPlugin();
|
|
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<qint64>((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 expose 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<qulonglong>(m_db->rootGroup()->timeInfo().creationTime().toMSecsSinceEpoch() / 1000));
|
|
COMPARE_DBUS_LOCAL_CALL(
|
|
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();
|
|
QVERIFY(service);
|
|
|
|
// no collections
|
|
COMPARE_DBUS_LOCAL_CALL(service->collections(), QList<Collection*>{});
|
|
}
|
|
|
|
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<Item*> locked;
|
|
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"Title", item->backend()->title()}}, locked));
|
|
QCOMPARE(locked.size(), 0);
|
|
QCOMPARE(unlocked, {item});
|
|
}
|
|
|
|
// search by attribute
|
|
{
|
|
QList<Item*> locked;
|
|
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"fdosecrets-test", "1"}}, locked));
|
|
QCOMPARE(locked.size(), 0);
|
|
QCOMPARE(unlocked, {item});
|
|
}
|
|
{
|
|
QList<Item*> 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<Item*> 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<Item*> locked;
|
|
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems({{"Password", item->backend()->password()}}, locked));
|
|
QCOMPARE(locked.size(), 0);
|
|
QCOMPARE(unlocked.size(), 0);
|
|
}
|
|
{
|
|
QList<Item*> 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<DatabaseOpenDialog*>();
|
|
QVERIFY(dbOpenDlg);
|
|
auto editPassword = dbOpenDlg->findChild<QLineEdit*>("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<QList<QDBusObjectPath>>(), {coll->objectPath()});
|
|
}
|
|
QCOMPARE(spyCollectionCreated.count(), 0);
|
|
QCOMPARE(spyCollectionChanged.count(), 1);
|
|
{
|
|
auto args = spyCollectionChanged.takeFirst();
|
|
QCOMPARE(args.size(), 1);
|
|
QCOMPARE(args.at(0).value<Collection*>(), 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<QList<QDBusObjectPath>>(), {});
|
|
}
|
|
{
|
|
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<QList<QDBusObjectPath>>(), {coll->objectPath()});
|
|
}
|
|
|
|
QCOMPARE(spyCollectionCreated.count(), 0);
|
|
QCOMPARE(spyCollectionChanged.count(), 1);
|
|
{
|
|
auto args = spyCollectionChanged.takeFirst();
|
|
QCOMPARE(args.size(), 1);
|
|
QCOMPARE(args.at(0).value<Collection*>(), 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<Collection>(args.at(1).value<QDBusObjectPath>());
|
|
QVERIFY(coll);
|
|
|
|
QCOMPARE(coll->backend()->database()->metadata()->name(), QStringLiteral("Test NewDB"));
|
|
|
|
QCOMPARE(spyCollectionCreated.count(), 1);
|
|
{
|
|
args = spyCollectionCreated.takeFirst();
|
|
QCOMPARE(args.size(), 1);
|
|
QCOMPARE(args.at(0).value<Collection*>(), coll);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TestGuiFdoSecrets::createDatabaseCallback()
|
|
{
|
|
auto wizard = m_tabWidget->findChild<NewDatabaseWizard*>();
|
|
QVERIFY(wizard);
|
|
|
|
QCOMPARE(wizard->currentId(), 0);
|
|
wizard->next();
|
|
wizard->next();
|
|
QCOMPARE(wizard->currentId(), 2);
|
|
|
|
// enter password
|
|
auto* passwordEdit = wizard->findChild<QLineEdit*>("enterPasswordEdit");
|
|
auto* passwordRepeatEdit = wizard->findChild<QLineEdit*>("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);
|
|
{
|
|
args = spyCollectionDeleted.takeFirst();
|
|
QCOMPARE(args.size(), 1);
|
|
QCOMPARE(args.at(0).value<Collection*>(), rawColl);
|
|
}
|
|
}
|
|
|
|
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
|
|
QVERIFY(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();
|
|
QVERIFY(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();
|
|
QVERIFY(service);
|
|
|
|
// collection is properly registered
|
|
auto coll = getDefaultCollection(service);
|
|
QVERIFY(coll->objectPath().path() != "/");
|
|
QCOMPARE(coll->name(), QStringLiteral(".Name"));
|
|
}
|
|
|
|
void TestGuiFdoSecrets::testDuplicateName()
|
|
{
|
|
QTemporaryDir dir;
|
|
QVERIFY(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();
|
|
QVERIFY(service);
|
|
|
|
// when two databases have the same name, one of it will have part of its uuid suffixed
|
|
const auto pathNoSuffix = QStringLiteral("/org/freedesktop/secrets/collection/KeePassXC");
|
|
CHECKED_DBUS_LOCAL_CALL(colls, service->collections());
|
|
QCOMPARE(colls.size(), 2);
|
|
QCOMPARE(colls[0]->objectPath().path(), pathNoSuffix);
|
|
QVERIFY(colls[1]->objectPath().path() != pathNoSuffix);
|
|
}
|
|
|
|
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<Item*> locked;
|
|
CHECKED_DBUS_LOCAL_CALL(unlocked, service->searchItems(attributes, locked));
|
|
QCOMPARE(locked, QList<Item*>{});
|
|
QCOMPARE(unlocked, QList<Item*>{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<Item*> 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<Item*> 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<Item*> 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<bool>(iitem));
|
|
|
|
auto replyMsg = iitem->call(QDBus::BlockWithGui, "GetSecret", QVariant::fromValue(sess->objectPath()));
|
|
auto reply = QDBusPendingReply<SecretStruct>(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);
|
|
{
|
|
args = spyItemDeleted.takeFirst();
|
|
QCOMPARE(args.size(), 1);
|
|
QCOMPARE(args.at(0).value<Item*>(), 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<Entry*> exposedEntries;
|
|
for (const auto& item : items.value()) {
|
|
exposedEntries << item->backend();
|
|
}
|
|
QCOMPARE(exposedEntries, subgroup->entries());
|
|
}
|
|
|
|
void TestGuiFdoSecrets::testModifyingExposedGroup()
|
|
{
|
|
// 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<Service> TestGuiFdoSecrets::enableService()
|
|
{
|
|
FdoSecrets::settings()->setEnabled(true);
|
|
VERIFY(m_plugin);
|
|
m_plugin->updateServiceState();
|
|
return m_plugin->serviceInstance();
|
|
}
|
|
|
|
QPointer<Session> 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<QDBusVariant, QDBusObjectPath>(replyMsg);
|
|
|
|
VERIFY(reply.isValid());
|
|
return FdoSecrets::pathToObject<Session>(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<QDBusVariant, QDBusObjectPath>(replyMsg);
|
|
VERIFY(reply.isValid());
|
|
COMPARE(qvariant_cast<QByteArray>(reply.argumentAt<0>().variant()), MpiToBytes(m_serverPublic));
|
|
return FdoSecrets::pathToObject<Session>(reply.argumentAt<1>());
|
|
}
|
|
FAIL("Unsupported algorithm");
|
|
}
|
|
|
|
QPointer<Collection> TestGuiFdoSecrets::getDefaultCollection(Service* service)
|
|
{
|
|
VERIFY(service);
|
|
auto coll = service->readAlias("default");
|
|
VERIFY(!coll.isError());
|
|
return coll.value();
|
|
}
|
|
|
|
QPointer<Item> TestGuiFdoSecrets::getFirstItem(Collection* coll)
|
|
{
|
|
VERIFY(coll);
|
|
auto items = coll->items();
|
|
VERIFY(!items.isError());
|
|
VERIFY(!items.value().isEmpty());
|
|
return items.value().at(0);
|
|
}
|
|
|
|
QPointer<Item> 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();
|
|
}
|