diff --git a/src/api/KWallet/org.freedesktop.Secrets.Collection.xml b/src/api/KWallet/org.freedesktop.Secrets.Collection.xml
new file mode 100644
index 0000000000000000000000000000000000000000..94dd9ab4029e53e8116165de238de6ef2f3dab3d
--- /dev/null
+++ b/src/api/KWallet/org.freedesktop.Secrets.Collection.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/KWallet/org.freedesktop.Secrets.Item.xml b/src/api/KWallet/org.freedesktop.Secrets.Item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8544bded14352331b07359f3593141c25eb6635e
--- /dev/null
+++ b/src/api/KWallet/org.freedesktop.Secrets.Item.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/KWallet/org.freedesktop.Secrets.Prompt.xml b/src/api/KWallet/org.freedesktop.Secrets.Prompt.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7a6ab6f0b5df96d214d9a883de3a536840f5a3d8
--- /dev/null
+++ b/src/api/KWallet/org.freedesktop.Secrets.Prompt.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/KWallet/org.freedesktop.Secrets.Service.xml b/src/api/KWallet/org.freedesktop.Secrets.Service.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ff68bbc4883bdf8fb54d58b7274d3df0bb1ae1d8
--- /dev/null
+++ b/src/api/KWallet/org.freedesktop.Secrets.Service.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/api/KWallet/org.freedesktop.Secrets.Session.xml b/src/api/KWallet/org.freedesktop.Secrets.Session.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cba5b6de993504a0dfa485117bcc58d32899b659
--- /dev/null
+++ b/src/api/KWallet/org.freedesktop.Secrets.Session.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/runtime/kwalletd/CMakeLists.txt b/src/runtime/kwalletd/CMakeLists.txt
index 81e4825a5d3a9887a2f4b33206743fd2d6a96711..a9ce5d812ee7d05e20a55b79934705cdb588b25f 100644
--- a/src/runtime/kwalletd/CMakeLists.txt
+++ b/src/runtime/kwalletd/CMakeLists.txt
@@ -1,5 +1,10 @@
project(kwalletd5)
+include(CheckSymbolExists)
+
+check_symbol_exists(explicit_bzero "string.h" KWALLETD_HAVE_EXPLICIT_BZERO)
+check_symbol_exists(RtlSecureZeroMemory "windows.h" KWALLETD_HAVE_RTLSECUREZEROMEMORY)
+
find_package(Qt${QT_MAJOR_VERSION} ${REQUIRED_QT_VERSION} CONFIG REQUIRED Gui)
find_package(KF5Config ${KF_DEP_VERSION} REQUIRED)
@@ -21,10 +26,12 @@ if (Gpgmepp_FOUND)
include_directories(${GPGME_INCLUDES})
endif(Gpgmepp_FOUND)
+find_package(Qca-qt${QT_MAJOR_VERSION} REQUIRED 2.3.1)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
########### build backends #########
add_subdirectory(backend)
+add_subdirectory(autotests)
########### kwalletd ###############
@@ -48,6 +55,12 @@ target_sources(kwalletd5 PRIVATE
kwalletwizard.cpp
ktimeout.cpp
kwalletsessionstore.cpp
+ kwalletfreedesktopservice.cpp
+ kwalletfreedesktopsession.cpp
+ kwalletfreedesktopcollection.cpp
+ kwalletfreedesktopitem.cpp
+ kwalletfreedesktopprompt.cpp
+ kwalletfreedesktopattributes.cpp
)
ecm_qt_declare_logging_category(kwalletd5
HEADER kwalletd_debug.h
@@ -85,9 +98,24 @@ else()
# copy of org.kde.KWallet.xml, but with all deprecated API removed
set(kwallet_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.kde.KWallet.nodeprecated.xml)
endif()
+set(fdo_service_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Service.xml)
+set(fdo_session_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Session.xml)
+set(fdo_collection_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Collection.xml)
+set(fdo_item_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Item.xml)
+set(fdo_prompt_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Prompt.xml)
set(kwalletd_dbus_SRCS)
qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${kwallet_xml} kwalletd.h KWalletD kwalletadaptor KWalletAdaptor)
+qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${fdo_service_xml} kwalletfreedesktopservice.h KWalletFreedesktopService
+ kwalletfreedesktopserviceadaptor KWalletFreedesktopServiceAdaptor)
+qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${fdo_session_xml} kwalletfreedesktopsession.h KWalletFreedesktopSession
+ kwalletfreedesktopsessionadaptor KWalletFreedesktopSessionAdaptor)
+qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${fdo_collection_xml} kwalletfreedesktopcollection.h KWalletFreedesktopCollection
+ kwalletfreedesktopcollectionadaptor KWalletFreedesktopCollectionAdaptor)
+qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${fdo_item_xml} kwalletfreedesktopitem.h KWalletFreedesktopItem
+ kwalletfreedesktopitemadaptor KWalletFreedesktopItemAdaptor)
+qt_add_dbus_adaptor(kwalletd_dbus_SRCS ${fdo_prompt_xml} kwalletfreedesktopprompt.h KWalletFreedesktopPrompt
+ kwalletfreedesktoppromptadaptor KWalletFreedesktopPromptAdaptor)
target_sources(kwalletd5 PRIVATE
${kwalletd_dbus_SRCS}
)
@@ -115,7 +143,8 @@ target_link_libraries(kwalletd5
KF5::DBusAddons
KF5::WidgetsAddons
KF5::WindowSystem
- KF5::Notifications)
+ KF5::Notifications
+ ${Qca_LIBRARY})
if (Gpgmepp_FOUND)
target_link_libraries(kwalletd5 Gpgmepp)
kde_target_enable_exceptions(kwalletd5 PRIVATE)
diff --git a/src/runtime/kwalletd/autotests/CMakeLists.txt b/src/runtime/kwalletd/autotests/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9ebea969ebe0bea8245f53f4ca6178a1ddfdf1e8
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/CMakeLists.txt
@@ -0,0 +1,78 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+find_package(Qt${QT_MAJOR_VERSION}Test REQUIRED)
+find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+set(CMAKE_AUTOMOC ON)
+
+include(ECMAddTests)
+
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/..)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/..)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../backend)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/../backend)
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../../../api/KWallet)
+include_directories(${CMAKE_CURRENT_BINARY_DIR}/../../../api/KWallet)
+
+add_definitions(-DFDO_ENABLE_DUMMY_MESSAGE_CONNECTION)
+remove_definitions(-DQT_NO_CAST_FROM_ASCII)
+
+if (NOT EXCLUDE_DEPRECATED_BEFORE_AND_AT STREQUAL "CURRENT" AND
+ EXCLUDE_DEPRECATED_BEFORE_AND_AT VERSION_LESS 5.72.0)
+ set(kwallet_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.kde.KWallet.xml)
+else()
+ # copy of org.kde.KWallet.xml, but with all deprecated API removed
+ set(kwallet_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.kde.KWallet.nodeprecated.xml)
+endif()
+set(fdo_service_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Service.xml)
+set(fdo_session_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Session.xml)
+set(fdo_collection_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Collection.xml)
+set(fdo_item_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Item.xml)
+set(fdo_prompt_xml ${CMAKE_SOURCE_DIR}/src/api/KWallet/org.freedesktop.Secrets.Prompt.xml)
+
+set(TEST_SRC
+ fdo_secrets_test.cpp
+ ../kwalletfreedesktopservice.cpp
+ ../kwalletfreedesktopitem.cpp
+ ../kwalletfreedesktopcollection.cpp
+ ../kwalletfreedesktopsession.cpp
+ ../kwalletfreedesktopprompt.cpp
+ ../kwalletfreedesktopattributes.cpp
+)
+
+qt_add_dbus_adaptor( TEST_SRC ${kwallet_xml} ../kwalletd.h KWalletD kwalletadaptor KWalletAdaptor)
+qt_add_dbus_adaptor( TEST_SRC ${fdo_service_xml} ../kwalletfreedesktopservice.h KWalletFreedesktopService
+ kwalletfreedesktopserviceadaptor KWalletFreedesktopServiceAdaptor)
+qt_add_dbus_adaptor( TEST_SRC ${fdo_session_xml} ../kwalletfreedesktopsession.h KWalletFreedesktopSession
+ kwalletfreedesktopsessionadaptor KWalletFreedesktopSessionAdaptor)
+qt_add_dbus_adaptor( TEST_SRC ${fdo_collection_xml} ../kwalletfreedesktopcollection.h KWalletFreedesktopCollection
+ kwalletfreedesktopcollectionadaptor KWalletFreedesktopCollectionAdaptor)
+qt_add_dbus_adaptor( TEST_SRC ${fdo_item_xml} ../kwalletfreedesktopitem.h KWalletFreedesktopItem
+ kwalletfreedesktopitemadaptor KWalletFreedesktopItemAdaptor)
+qt_add_dbus_adaptor( TEST_SRC ${fdo_prompt_xml} ../kwalletfreedesktopprompt.h KWalletFreedesktopPrompt
+ kwalletfreedesktoppromptadaptor KWalletFreedesktopPromptAdaptor)
+
+ecm_add_test(
+ ${TEST_SRC}
+ ../kwalletfreedesktopservice.h
+ ../kwalletfreedesktopcollection.h
+ ../kwalletfreedesktopitem.h
+ ../kwalletfreedesktopsession.h
+ ../kwalletfreedesktopprompt.h
+ ../kwalletd.h
+ ../ktimeout.h
+ kwalletfreedesktopserviceadaptor.cpp
+ kwalletfreedesktopcollectionadaptor.cpp
+ kwalletfreedesktopitemadaptor.cpp
+ kwalletfreedesktopsessionadaptor.cpp
+ kwalletfreedesktoppromptadaptor.cpp
+ TEST_NAME fdo_secrets_test
+ LINK_LIBRARIES
+ KF5Wallet
+ kwalletbackend5
+ Qt${QT_MAJOR_VERSION}::Widgets
+ Qt${QT_MAJOR_VERSION}::Test
+ KF5::DBusAddons
+ KF5::ConfigCore
+ ${Qca_LIBRARY}
+)
diff --git a/src/runtime/kwalletd/autotests/fdo_secrets_test.cpp b/src/runtime/kwalletd/autotests/fdo_secrets_test.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..bc8b424336e31e462f6c7d8f770440526210cc7f
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/fdo_secrets_test.cpp
@@ -0,0 +1,491 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "fdo_secrets_test.h"
+#include "mockkwalletd.cpp"
+#include "static_mock.hpp"
+
+void FdoSecretsTest::initTestCase()
+{
+ static QCA::Initializer init{};
+}
+
+void FdoSecretsTest::serviceStaticFunctions()
+{
+ auto labels = Testset{
+ {"label", {"label", -1}},
+ {"label__0_", {"label", 0}},
+ {"label___1_", {"label_", 1}},
+ {"__0_label___200_", {"__0_label_", 200}},
+ };
+
+ runTestset(FdoUniqueLabel::fromName, labels);
+ runRevTestset(
+ [](const FdoUniqueLabel &l) {
+ return l.toName();
+ },
+ labels);
+
+ runTestset(KWalletFreedesktopService::wrapToCollectionPath,
+ Testset{
+ {"/org/freedesktop/secrets/collection/abcd", "/org/freedesktop/secrets/collection/abcd"},
+ {"/org/freedesktop/secrets/collection/abcd/2", "/org/freedesktop/secrets/collection/abcd"},
+ {"/org/freedesktop/secrets/collection/abcd/2/2/3/4", "/org/freedesktop/secrets/collection/abcd"},
+ });
+
+ QCOMPARE(KWalletFreedesktopService::nextPromptPath().path(), "/org/freedesktop/secrets/prompt/p0");
+ QCOMPARE(KWalletFreedesktopService::nextPromptPath().path(), "/org/freedesktop/secrets/prompt/p1");
+}
+
+void FdoSecretsTest::collectionStaticFunctions()
+{
+ auto dirNameTestset = Testset{
+ {{FDO_SECRETS_DEFAULT_DIR, "entry1"}, {"entry1", -1}},
+ {{FDO_SECRETS_DEFAULT_DIR, "entry__3_"}, {"entry", 3}},
+ {{"Passwords", "password__"}, {"Passwords/password__", -1}},
+ {{"Passwords__3_", "password__200_"}, {"Passwords__3_/password", 200}},
+ {{"", "password"}, {"/password", -1}},
+ {{"", "/"}, {"//", -1}},
+ {{FDO_SECRETS_DEFAULT_DIR, "/"}, {"/", -1}},
+ {{FDO_SECRETS_DEFAULT_DIR, "/__2_"}, {"/", 2}},
+ {{"https:", "/foobar.org/"}, {"https://foobar.org/", -1}},
+ {{"https:", "/foobar.org/__80_"}, {"https://foobar.org/", 80}},
+ };
+
+ runTestset(
+ [](const EntryLocation &l) {
+ return l.toUniqueLabel();
+ },
+ dirNameTestset);
+ runRevTestset(
+ [](const FdoUniqueLabel &l) {
+ return l.toEntryLocation();
+ },
+ dirNameTestset);
+}
+
+void FdoSecretsTest::cleanup()
+{
+ SET_FUNCTION_RESULT(KWalletD::wallets, QStringList());
+}
+
+void FdoSecretsTest::precreatedWallets()
+{
+ const QStringList wallets = {"wallet1", "wallet2", "wallet2__0_", "wallet2__1_"};
+ SET_FUNCTION_RESULT(KWalletD::wallets, wallets);
+ SET_FUNCTION_RESULT_OVERLOADED(KWalletD::isOpen, true, bool (KWalletD::*)(int));
+
+ std::unique_ptr kwalletd{new KWalletD};
+ std::unique_ptr service{new KWalletFreedesktopService(kwalletd.get())};
+
+ QCOMPARE(wallets.size(), service->collections().size());
+ for (const auto &walletName : wallets) {
+ auto collection = service->getCollectionByWalletName(walletName);
+ QVERIFY(collection);
+ QVERIFY(collection->label() == "wallet1" || collection->label() == "wallet2");
+ }
+
+ auto firstCollection = service->getCollectionByWalletName(wallets.front());
+ auto &item1 = firstCollection->pushNewItem(FdoUniqueLabel{"item1", -1}, QDBusObjectPath(firstCollection->fdoObjectPath().path() + "/0"));
+ QCOMPARE(item1.fdoObjectPath().path(), (firstCollection->fdoObjectPath().path() + "/0"));
+ QCOMPARE(&item1, service->getItemByObjectPath(item1.fdoObjectPath()));
+}
+
+void FdoSecretsTest::aliases()
+{
+ std::unique_ptr kwalletd{new KWalletD};
+ std::unique_ptr service{new KWalletFreedesktopService(kwalletd.get())};
+
+ service->createCollectionAlias("alias", "walletName");
+ service->createCollectionAlias("alias2", "walletName");
+ service->createCollectionAlias("alias3", "walletName300");
+ service->updateCollectionAlias("alias3", "walletName");
+ QSet checkAliases = {"alias", "alias2", "alias3"};
+ const QStringList aliases = service->readAliasesFor("walletName");
+ for (const auto &alias : aliases)
+ checkAliases.remove(alias);
+ QVERIFY(checkAliases.isEmpty());
+
+ service->removeAlias("alias");
+ service->removeAlias("alias2");
+ service->removeAlias("alias3");
+ QVERIFY(service->readAliasesFor("walletName").isEmpty());
+}
+
+struct SetupSessionT {
+ QDBusObjectPath sessionPath;
+ QCA::SymmetricKey symmetricKey;
+ QByteArray error;
+};
+
+#define SETUP_SESSION_VERIFY(cond) \
+ do { \
+ if (!(cond)) \
+ return SetupSessionT{QDBusObjectPath(), QCA::SymmetricKey(), #cond}; \
+ } while (false)
+
+SetupSessionT setupSession(KWalletFreedesktopService *service)
+{
+ SetupSessionT result;
+ QCA::KeyGenerator keygen;
+ auto dlGroup = QCA::DLGroup(keygen.createDLGroup(QCA::IETF_1024));
+ if (dlGroup.isNull()) {
+ result.error = "createDLGroup failed, maybe libqca-ossl is missing";
+ return result;
+ }
+
+ auto privateKey = QCA::PrivateKey(keygen.createDH(dlGroup));
+ auto publicKey = QCA::PublicKey(privateKey);
+
+ auto connection = QDBusConnection::sessionBus();
+ auto message = QDBusMessage::createSignal("dummy", "dummy", "dummy");
+
+ auto pubKeyBytes = publicKey.toDH().y().toArray().toByteArray();
+ auto sessionPubKeyVariant = service->OpenSession("dh-ietf1024-sha256-aes128-cbc-pkcs7", QDBusVariant(pubKeyBytes), result.sessionPath);
+ SETUP_SESSION_VERIFY(result.sessionPath.path() != "/");
+ SETUP_SESSION_VERIFY(sessionPubKeyVariant.variant().canConvert());
+
+ auto servicePublicKeyBytes = sessionPubKeyVariant.variant().toByteArray();
+ SETUP_SESSION_VERIFY(!servicePublicKeyBytes.isEmpty());
+
+ auto servicePublicKey = QCA::DHPublicKey(dlGroup, QCA::BigInteger(QCA::SecureArray(servicePublicKeyBytes)));
+ auto commonSecret = privateKey.deriveKey(servicePublicKey);
+ result.symmetricKey = QCA::HKDF().makeKey(commonSecret, {}, {}, FDO_SECRETS_CIPHER_KEY_SIZE);
+
+ return result;
+}
+
+void FdoSecretsTest::items()
+{
+ const QStringList wallets = {"wallet1"};
+ const QStringList folders = {FDO_SECRETS_DEFAULT_DIR};
+ const QStringList entries = {"item1", "item2", "item3"};
+ SET_FUNCTION_RESULT(KWalletD::wallets, wallets);
+ SET_FUNCTION_RESULT(KWalletD::folderList, folders);
+ SET_FUNCTION_RESULT(KWalletD::entryList, entries);
+
+ SET_FUNCTION_IMPL(KWalletD::entryType, [](int, const QString &, const QString &key, const QString &) -> int {
+ if (key == "item1")
+ return KWallet::Wallet::Password;
+ else if (key == "item2")
+ return KWallet::Wallet::Map;
+ else if (key == "item3")
+ return KWallet::Wallet::Stream;
+ else
+ QTEST_ASSERT(false);
+ });
+
+ QString _secretHolder1 = "It's a password";
+ QByteArray _secretHolder2;
+ QByteArray _secretHolder3;
+
+ {
+ QByteArray a = "It's a";
+ QString b = "stream";
+
+ QDataStream ds{&_secretHolder2, QIODevice::WriteOnly};
+ ds << a << b;
+ }
+
+ {
+ StrStrMap map;
+ map["it's a"] = "map";
+
+ QDataStream ds{&_secretHolder3, QIODevice::WriteOnly};
+ ds << map;
+ }
+
+ SET_FUNCTION_IMPL(KWalletD::readPassword, [&](int, const QString &, const QString &key, const QString &) -> QString {
+ QTEST_ASSERT(key == "item1");
+ return _secretHolder1;
+ });
+
+ SET_FUNCTION_IMPL(KWalletD::readEntry, [&](int, const QString &, const QString &key, const QString &) -> QByteArray {
+ QTEST_ASSERT(key == "item3" || key == "item2");
+ if (key == "item2")
+ return _secretHolder2;
+ else
+ return _secretHolder3;
+ });
+
+ SET_FUNCTION_IMPL(KWalletD::writePassword, [&](int, const QString &, const QString &key, const QString &value, const QString &) -> int {
+ QTEST_ASSERT(key == "item1");
+ _secretHolder1 = value;
+ return 0;
+ });
+
+ using writeEntryT = int (KWalletD::*)(int, const QString &, const QString &, const QByteArray &, int, const QString &);
+ SET_FUNCTION_IMPL_OVERLOADED(KWalletD::writeEntry,
+ writeEntryT,
+ [&](int, const QString &, const QString &key, const QByteArray &value, int, const QString &) -> int {
+ QTEST_ASSERT(key == "item3" || key == "item2");
+ if (key == "item2")
+ _secretHolder2 = value;
+ else
+ _secretHolder3 = value;
+ return 0;
+ });
+
+ std::unique_ptr kwalletd{new KWalletD};
+ std::unique_ptr service{new KWalletFreedesktopService(kwalletd.get())};
+
+ auto collection = service->getCollectionByWalletName("wallet1");
+ QVERIFY(collection);
+
+ /* Write some attributes */
+ {
+ collection->itemAttributes().newItem({FDO_SECRETS_DEFAULT_DIR, "item1"});
+ collection->itemAttributes().newItem({FDO_SECRETS_DEFAULT_DIR, "item2"});
+ collection->itemAttributes().newItem({FDO_SECRETS_DEFAULT_DIR, "item3"});
+ collection->itemAttributes().setParam({FDO_SECRETS_DEFAULT_DIR, "item3"}, FDO_KEY_CREATED, 100200300ULL);
+ collection->itemAttributes().setParam({FDO_SECRETS_DEFAULT_DIR, "item3"}, FDO_KEY_MODIFIED, 100200301ULL);
+ auto attribs = collection->itemAttributes().getAttributes({FDO_SECRETS_DEFAULT_DIR, "item3"});
+ attribs["Attrib1"] = "value1";
+ attribs["Attrib2"] = "value2";
+ collection->itemAttributes().setAttributes({FDO_SECRETS_DEFAULT_DIR, "item3"}, attribs);
+ }
+
+ /* Create collection */
+ using OpenAsyncT = int (KWalletD::*)(const QString &, qlonglong, const QString &, bool, const QDBusConnection &, const QDBusMessage &);
+ bool openAsyncCalled = false;
+ SET_FUNCTION_IMPL_OVERLOADED(KWalletD::openAsync,
+ OpenAsyncT,
+ [&](const QString &, qlonglong, const QString &, bool, const QDBusConnection &, const QDBusMessage &) -> int {
+ openAsyncCalled = true;
+ return 0;
+ });
+
+ QDBusObjectPath promptPath;
+ service->Unlock({collection->fdoObjectPath()}, promptPath);
+ auto prompt = service->getPromptByObjectPath(promptPath);
+ QVERIFY(prompt);
+ prompt->Prompt("wndid");
+ Q_EMIT kwalletd->walletAsyncOpened(0, 0);
+ SET_FUNCTION_RESULT_OVERLOADED(KWalletD::isOpen, true, bool (KWalletD::*)(int));
+ QVERIFY(!collection->locked());
+
+ auto item1 = collection->findItemByEntryLocation({FDO_SECRETS_DEFAULT_DIR, "item1"});
+ auto item2 = collection->findItemByEntryLocation({FDO_SECRETS_DEFAULT_DIR, "item2"});
+ auto item3 = collection->findItemByEntryLocation({FDO_SECRETS_DEFAULT_DIR, "item3"});
+ QVERIFY(item1 && item2 && item3);
+
+ auto message = QDBusMessage::createSignal("dummy", "dummy", "dummy");
+ auto [sessionPath, symmetricKey, errorStr] = setupSession(service.get());
+ QVERIFY2(errorStr.isEmpty(), errorStr.constData());
+
+ /* Check secrets */
+ auto secret1 = item1->GetSecret(sessionPath);
+ service->desecret(message, secret1);
+ QCOMPARE(secret1.note.toByteArray(), "It's a password");
+ // QCOMPARE(secret1.mimeType, "text/plain");
+
+ auto secret2 = item2->GetSecret(sessionPath);
+ service->desecret(message, secret2);
+ QByteArray secretBytes = secret2.note.toByteArray();
+ QDataStream ds{secretBytes};
+ QByteArray a;
+ QString b;
+ ds >> a >> b;
+
+ QCOMPARE(secret2.mimeType, "application/octet-stream");
+ QCOMPARE(a, "It's a");
+ QCOMPARE(b, "stream");
+
+ auto secret3 = item3->GetSecret(sessionPath);
+ service->desecret(message, secret3);
+ auto bytes3 = secret3.note.toByteArray();
+ QDataStream ds2(bytes3);
+ StrStrMap map3;
+ ds2 >> map3;
+
+ QVERIFY(map3.find("it's a") != map3.end() && map3["it's a"] == "map");
+ QCOMPARE(item3->created(), 100200300);
+ QCOMPARE(item3->modified(), 100200301);
+ QCOMPARE(item3->attributes()["Attrib1"], "value1");
+ QCOMPARE(item3->attributes()["Attrib2"], "value2");
+
+ /* Set new secrets */
+ secret1.note = QByteArray("It's a new password");
+ secret1.mimeType = "text/plain";
+ service->ensecret(message, secret1);
+ item1->SetSecret(secret1);
+ secret1 = item1->GetSecret(sessionPath);
+ service->desecret(message, secret1);
+ QCOMPARE(secret1.note.toByteArray(), "It's a new password");
+ QCOMPARE(secret1.mimeType, "text/plain");
+
+ secret2.note = QByteArray("It's a new secret");
+ secret2.mimeType = "application/octet-stream";
+ service->ensecret(message, secret2);
+ item2->SetSecret(secret2);
+ auto attribs = item2->attributes();
+ attribs["newAttrib"] = ")))";
+ item2->setAttributes(attribs);
+
+ secret2 = item2->GetSecret(sessionPath);
+ service->desecret(message, secret2);
+ QCOMPARE(secret2.note.toByteArray(), "It's a new secret");
+ QCOMPARE(item2->attributes()["newAttrib"], ")))");
+
+ /* Search items */
+ attribs.clear();
+ attribs["Attrib1"] = "value1";
+ QList lockedItems;
+ auto unlockedItems = service->SearchItems(attribs, lockedItems);
+ QCOMPARE(unlockedItems.size(), 1);
+ QCOMPARE(unlockedItems.front(), item3->fdoObjectPath());
+}
+
+void FdoSecretsTest::createLockUnlockCollection()
+{
+ std::unique_ptr kwalletd{new KWalletD};
+ std::unique_ptr service{new KWalletFreedesktopService(kwalletd.get())};
+
+ /* Create collection */
+ using OpenAsyncT = int (KWalletD::*)(const QString &, qlonglong, const QString &, bool, const QDBusConnection &, const QDBusMessage &);
+ bool openAsyncCalled = false;
+ SET_FUNCTION_IMPL_OVERLOADED(KWalletD::openAsync,
+ OpenAsyncT,
+ [&](const QString &, qlonglong, const QString &, bool, const QDBusConnection &, const QDBusMessage &) -> int {
+ openAsyncCalled = true;
+ return 0;
+ });
+
+ QVariantMap props;
+ props["org.freedesktop.Secret.Collection.Label"] = QString("walletName");
+ QDBusObjectPath promptPath;
+ service->CreateCollection(props, "", promptPath);
+ auto prompt = service->getPromptByObjectPath(promptPath);
+ QVERIFY(prompt);
+ prompt->Prompt("wndid");
+ QVERIFY(openAsyncCalled);
+ Q_EMIT kwalletd->walletAsyncOpened(0, 0);
+
+ auto createdCollection = service->getCollectionByWalletName("walletName");
+ QVERIFY(createdCollection);
+ QCOMPARE(createdCollection->label(), "walletName");
+
+ /* Check aliases */
+ service->createCollectionAlias("alias", "walletName");
+ service->createCollectionAlias("alias2", "walletName");
+ service->createCollectionAlias("alias3", "walletName");
+
+ QCOMPARE(service->resolveIfAlias(QStringLiteral(FDO_ALIAS_PATH) + "alias"), createdCollection->fdoObjectPath().path());
+ QCOMPARE(service->resolveIfAlias(QStringLiteral(FDO_ALIAS_PATH) + "alias2"), createdCollection->fdoObjectPath().path());
+ QCOMPARE(service->resolveIfAlias(QStringLiteral(FDO_ALIAS_PATH) + "alias3"), createdCollection->fdoObjectPath().path());
+ QCOMPARE(service->ReadAlias("alias"), createdCollection->fdoObjectPath());
+
+ service->removeAlias("alias");
+ service->removeAlias("alias2");
+ service->removeAlias("alias3");
+
+ /* Lock/Unlock */
+ auto lockedObjects = service->Lock({createdCollection->fdoObjectPath()}, promptPath);
+ QCOMPARE(lockedObjects.size(), 1);
+ QCOMPARE(lockedObjects.front(), createdCollection->fdoObjectPath());
+ SET_FUNCTION_RESULT_OVERLOADED(KWalletD::isOpen, false, bool (KWalletD::*)(int));
+ QVERIFY(createdCollection->locked());
+
+ service->Unlock({createdCollection->fdoObjectPath()}, promptPath);
+ prompt = service->getPromptByObjectPath(promptPath);
+ QVERIFY(prompt);
+ openAsyncCalled = false;
+ prompt->Prompt("wndid");
+ QVERIFY(openAsyncCalled);
+ Q_EMIT kwalletd->walletAsyncOpened(0, 0);
+ SET_FUNCTION_RESULT_OVERLOADED(KWalletD::isOpen, true, bool (KWalletD::*)(int));
+ QVERIFY(!createdCollection->locked());
+}
+
+void FdoSecretsTest::session()
+{
+ std::unique_ptr kwalletd{new KWalletD};
+ std::unique_ptr service{new KWalletFreedesktopService(kwalletd.get())};
+
+ auto message = QDBusMessage::createSignal("dummy", "dummy", "dummy");
+ auto [sessionPath, symmetricKey, errorStr] = setupSession(service.get());
+
+ /* Generate secret */
+ auto secret = FreedesktopSecret(sessionPath, QByteArray("It's a secret"), "text/plain");
+ QVERIFY(service->ensecret(message, secret));
+
+ /* Try to decrypt by hand with symmetricKey */
+ auto cipher = QCA::Cipher("aes128", QCA::Cipher::CBC, QCA::Cipher::PKCS7, QCA::Decode, symmetricKey, secret.initVector);
+ QCA::SecureArray result;
+ result.append(cipher.update(QCA::MemoryRegion(secret.note.toByteArray())));
+ result.append(cipher.final());
+
+ QCOMPARE(QString::fromUtf8(result.toByteArray()), "It's a secret");
+
+ /* Try to decrypt by session */
+ QVERIFY(service->desecret(message, secret));
+ QCOMPARE(secret.note.toByteArray(), QByteArray("It's a secret"));
+}
+
+void FdoSecretsTest::attributes()
+{
+ KWalletFreedesktopAttributes attribs{"test"};
+
+ attribs.newItem({"dir", "name"});
+
+ attribs.setParam({"dir", "name"}, "param1", 0xff00ff00ff00ff00);
+ attribs.setParam({"dir", "name"}, "param2", "string_param");
+
+ QCOMPARE(attribs.getULongLongParam({"dir", "name"}, "param1", 0), 0xff00ff00ff00ff00);
+ QCOMPARE(attribs.getStringParam({"dir", "name"}, "param2", ""), "string_param");
+
+ attribs.renameLabel({"dir", "name"}, {"newdir", "newname"});
+
+ QCOMPARE(attribs.getULongLongParam({"newdir", "newname"}, "param1", 0), 0xff00ff00ff00ff00);
+ QCOMPARE(attribs.getStringParam({"newdir", "newname"}, "param2", ""), "string_param");
+ QCOMPARE(attribs.getULongLongParam({"dir", "name"}, "param1", 0xdef017), 0xdef017);
+ QCOMPARE(attribs.getStringParam({"dir", "name"}, "param2", "default"), "default");
+
+ attribs.setParam({"newdir", "newname"}, "param1", 100200300ULL);
+ attribs.setParam({"newdir", "newname"}, "param2", "another_string_param");
+
+ QCOMPARE(attribs.getULongLongParam({"newdir", "newname"}, "param1", 0), 100200300ULL);
+ QCOMPARE(attribs.getStringParam({"newdir", "newname"}, "param2", ""), "another_string_param");
+
+ QVERIFY(attribs.getAttributes({"newdir", "newname"}).empty());
+
+ StrStrMap attribMap;
+ attribMap["key1"] = "value1";
+ attribMap["key2"] = "value2";
+
+ attribs.setAttributes({"newdir", "newname"}, attribMap);
+ QCOMPARE(attribs.getAttributes({"newdir", "newname"}), attribMap);
+
+ attribs.setAttributes({"dir", "name"}, attribMap);
+ /* Item not exists - expects empty attributes map */
+ QVERIFY(attribs.getAttributes({"dir", "name"}).empty());
+
+ attribs.setParam({"dir1", "name1"}, "param1", "some_param");
+ QCOMPARE(attribs.getStringParam({"dir1", "name1"}, "param1", "default"), "default");
+}
+
+void FdoSecretsTest::walletNameEncodeDecode()
+{
+#define ENCODE_DECODE_CHECK(DECODED, ENCODED) \
+ do { \
+ auto encodedResult = KWallet::Backend::encodeWalletName(DECODED); \
+ auto decodedResult = KWallet::Backend::decodeWalletName(ENCODED); \
+ QCOMPARE(encodedResult, ENCODED); \
+ QCOMPARE(decodedResult, DECODED); \
+ } while (false)
+
+ ENCODE_DECODE_CHECK("/", ";2F");
+ QString allowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^&'@{}[],$=!-#()%.+_\r\n\t\f\v ";
+ ENCODE_DECODE_CHECK(allowedChars, allowedChars);
+ ENCODE_DECODE_CHECK("a/b/c\\", "a;2Fb;2Fc;5C");
+ ENCODE_DECODE_CHECK("/\\/", ";2F;5C;2F");
+ ENCODE_DECODE_CHECK(";;;", ";3B;3B;3B");
+ ENCODE_DECODE_CHECK(";3B", ";3B3B");
+
+#undef ENCODE_DECODE_CHECK
+}
+
+QTEST_GUILESS_MAIN(FdoSecretsTest)
diff --git a/src/runtime/kwalletd/autotests/fdo_secrets_test.h b/src/runtime/kwalletd/autotests/fdo_secrets_test.h
new file mode 100644
index 0000000000000000000000000000000000000000..9ab236c2e987df4e7164ff6a8771ea04d988078b
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/fdo_secrets_test.h
@@ -0,0 +1,28 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "../kwalletd.h"
+#include "testhelpers.hpp"
+
+class FdoSecretsTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+ void cleanup();
+
+ void serviceStaticFunctions();
+ void collectionStaticFunctions();
+
+ void precreatedWallets();
+ void aliases();
+ void createLockUnlockCollection();
+ void items();
+ void session();
+ void attributes();
+ void walletNameEncodeDecode();
+};
diff --git a/src/runtime/kwalletd/autotests/mockkwalletd.cpp b/src/runtime/kwalletd/autotests/mockkwalletd.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6264ce1f2d3a2d1c16c603cbd04c2b6ffcbaf5c6
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/mockkwalletd.cpp
@@ -0,0 +1,153 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2022 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "static_mock.hpp"
+
+#include "../kwalletd.h"
+#include "../kwalletfreedesktopcollection.h"
+#include "../kwalletfreedesktopitem.h"
+#include "../kwalletfreedesktopprompt.h"
+#include "../kwalletfreedesktopservice.h"
+#include "../kwalletfreedesktopsession.h"
+
+KWalletD::KWalletD()
+ : _syncTime(0)
+{
+}
+KWalletD::~KWalletD()
+{
+}
+
+KWalletSessionStore::KWalletSessionStore()
+{
+}
+KWalletSessionStore::~KWalletSessionStore()
+{
+}
+
+KTimeout::KTimeout(QObject *)
+{
+}
+KTimeout::~KTimeout()
+{
+}
+
+MOCK_FUNCTION(KWalletD, encodeWalletName, 1, );
+MOCK_FUNCTION(KWalletD, decodeWalletName, 1, );
+
+MOCK_FUNCTION(KTimeout, clear, 0, );
+MOCK_FUNCTION(KTimeout, resetTimer, 2, );
+MOCK_FUNCTION(KTimeout, removeTimer, 1, );
+MOCK_FUNCTION(KTimeout, addTimer, 2, );
+
+void KTimeout::timerEvent(QTimerEvent *)
+{
+}
+
+MOCK_FUNCTION(KWalletD, readEntry, 4, );
+MOCK_FUNCTION(KWalletD, readMap, 4, );
+MOCK_FUNCTION(KWalletD, readPassword, 4, );
+
+MOCK_FUNCTION_RES(KWalletD, removeEntry, 4, 0, );
+MOCK_FUNCTION_RES(KWalletD, writeMap, 5, 0, );
+MOCK_FUNCTION_RES(KWalletD, writePassword, 5, 0, );
+
+using OVWriteEntry_6 = int (KWalletD::*)(int, const QString &, const QString &, const QByteArray &, int, const QString &);
+MOCK_FUNCTION_OVERLOADED_RES(KWalletD, writeEntry, 6, 0, OVWriteEntry_6);
+
+using OVWriteEntry_5 = int (KWalletD::*)(int, const QString &, const QString &, const QByteArray &, const QString &);
+MOCK_FUNCTION_OVERLOADED_RES(KWalletD, writeEntry, 5, 0, OVWriteEntry_5);
+
+MOCK_FUNCTION(KWalletD, entryType, 4, );
+MOCK_FUNCTION_RES(KWalletD, renameEntry, 5, 0, );
+
+MOCK_FUNCTION(KWalletD, isEnabled, 0, const);
+MOCK_FUNCTION(KWalletD, open, 3, );
+MOCK_FUNCTION(KWalletD, openPath, 3, );
+MOCK_FUNCTION(KWalletD, openPathAsync, 4, );
+
+using OVOpenAsync4 = int (KWalletD::*)(const QString &, qlonglong, const QString &, bool);
+MOCK_FUNCTION_OVERLOADED(KWalletD, openAsync, 4, OVOpenAsync4);
+
+using OVOpenAsync6 = int (KWalletD::*)(const QString &, qlonglong, const QString &, bool, const QDBusConnection &, const QDBusMessage &);
+MOCK_FUNCTION_OVERLOADED(KWalletD, openAsync, 6, OVOpenAsync6);
+
+using OVClose4 = int (KWalletD::*)(int, bool, const QString &, const QDBusMessage &);
+MOCK_FUNCTION_OVERLOADED(KWalletD, close, 4, OVClose4);
+
+using OVClose2 = int (KWalletD::*)(const QString &, bool);
+MOCK_FUNCTION_OVERLOADED(KWalletD, close, 2, OVClose2);
+
+using OVClose3 = int (KWalletD::*)(int, bool, const QString &);
+MOCK_FUNCTION_OVERLOADED(KWalletD, close, 3, OVClose3);
+
+MOCK_FUNCTION(KWalletD, deleteWallet, 1, );
+
+MOCK_FUNCTION_OVERLOADED(KWalletD, isOpen, 1, bool (KWalletD::*)(const QString &));
+MOCK_FUNCTION_OVERLOADED(KWalletD, isOpen, 1, bool (KWalletD::*)(int));
+
+MOCK_FUNCTION(KWalletD, users, 1, const);
+MOCK_FUNCTION(KWalletD, wallets, 0, const);
+MOCK_FUNCTION(KWalletD, folderList, 2, );
+MOCK_FUNCTION(KWalletD, hasFolder, 3, );
+MOCK_FUNCTION(KWalletD, createFolder, 3, );
+MOCK_FUNCTION(KWalletD, removeFolder, 3, );
+MOCK_FUNCTION(KWalletD, entryList, 3, );
+
+#if KWALLET_BUILD_DEPRECATED_SINCE(5, 72)
+MOCK_FUNCTION(KWalletD, readEntryList, 4, );
+MOCK_FUNCTION(KWalletD, readMapList, 4, );
+MOCK_FUNCTION(KWalletD, readPasswordList, 4, );
+#endif
+
+MOCK_FUNCTION(KWalletD, entriesList, 3, );
+MOCK_FUNCTION(KWalletD, mapList, 3, );
+MOCK_FUNCTION(KWalletD, passwordList, 3, );
+MOCK_FUNCTION(KWalletD, renameWallet, 2, );
+MOCK_FUNCTION(KWalletD, hasEntry, 4, );
+MOCK_FUNCTION(KWalletD, disconnectApplication, 2, );
+MOCK_FUNCTION(KWalletD, folderDoesNotExist, 2, );
+MOCK_FUNCTION(KWalletD, keyDoesNotExist, 3, );
+MOCK_FUNCTION(KWalletD, networkWallet, 0, );
+MOCK_FUNCTION(KWalletD, localWallet, 0, );
+MOCK_FUNCTION(KWalletD, pamOpen, 3, );
+MOCK_FUNCTION(KWalletD, sync, 2, );
+MOCK_FUNCTION(KWalletD, changePassword, 3, );
+MOCK_FUNCTION(KWalletD, reconfigure, 0, );
+MOCK_FUNCTION(KWalletD, closeAllWallets, 0, );
+MOCK_FUNCTION(KWalletD, screenSaverChanged, 1, );
+
+void KWalletD::registerKWalletd4Service()
+{
+}
+void KWalletD::slotServiceOwnerChanged(const QString &, const QString &, const QString &)
+{
+}
+void KWalletD::emitWalletListDirty()
+{
+}
+void KWalletD::timedOutClose(int)
+{
+}
+void KWalletD::timedOutSync(int)
+{
+}
+void KWalletD::notifyFailures()
+{
+}
+void KWalletD::processTransactions()
+{
+}
+void KWalletD::activatePasswordDialog()
+{
+}
+
+#include
+const QLoggingCategory &KWALLETD_LOG()
+{
+ static const QLoggingCategory category("kf.wallet.kwalletd", QtFatalMsg);
+ return category;
+}
diff --git a/src/runtime/kwalletd/autotests/static_mock.hpp b/src/runtime/kwalletd/autotests/static_mock.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4109474867713af207800dcf4a1d26b7d3325169
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/static_mock.hpp
@@ -0,0 +1,304 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+
+#ifndef __STATIC_MOCK__H__
+#define __STATIC_MOCK__H__
+
+#include
+#include
+#include
+#include
+
+/*
+ * Details
+ */
+
+#define COMPTIME_HASH(x) details::fnv1a64_hash(x)
+
+namespace details
+{
+template
+constexpr uint64_t fnv1a64_hash(const char *str) {
+ return (fnv1a64_hash(str) ^ static_cast(str[I])) * 0x100000001b3;
+}
+
+template <>
+constexpr uint64_t fnv1a64_hash(const char*) {
+ return 0xcbf29ce484222325;
+}
+
+template
+struct ElementType_ {
+ using type = typename std::tuple_element>::type;
+};
+
+template
+struct ElementType_ {
+ using type = std::nullptr_t;
+};
+
+template
+struct ElementType : details::ElementType_<(N < sizeof...(ArgsT)), N, ArgsT...> {};
+
+template
+struct FunctionTraitsImpl {
+ using ReturnType = ReturnT;
+
+ template using ArgT = typename ElementType::type;
+
+ static constexpr size_t arity = sizeof...(ArgsT);
+};
+
+template struct FunctionTraits {};
+
+template
+struct FunctionTraits : FunctionTraitsImpl {};
+
+template
+struct FunctionTraits
+ : FunctionTraitsImpl {};
+
+template
+struct FunctionTraits : FunctionTraitsImpl {};
+
+template
+static T& returnStaticStorage() {
+ static T value;
+ return value;
+}
+
+template
+struct ReturnStaticStorageHelper {
+ using TT = typename std::decay::type;
+
+ static T get() { return returnStaticStorage(); }
+ template
+ static void setValue(V&& v) { returnStaticStorage() = std::forward(v); }
+};
+
+template
+struct ReturnStaticStorageHelper {
+ static void get() {}
+ template
+ static void setValue(V) {}
+};
+
+template
+struct DefaultCtorOrNullForVoid {
+ using TT = typename std::decay::type;
+ static TT get() { return TT(); }
+};
+
+template <>
+struct DefaultCtorOrNullForVoid {
+ static std::nullptr_t get() { return nullptr; }
+};
+
+template
+struct FuncImplHelper {
+ using RetT = typename FunctionTraits::ReturnType;
+
+ static typename FunctionTraits::ReturnType call(ArgsT... args) {
+ if (func())
+ return func()(args...);
+ else
+ return ReturnStaticStorageHelper<
+ MemberF, NameHash, typename FunctionTraits::ReturnType>::get();
+ }
+
+ static std::function& func() {
+ static std::function holder;
+ return holder;
+ }
+};
+
+template
+struct FuncImpl
+ : std::conditional<
+ (I < FunctionTraits::arity),
+ FuncImpl::template ArgT>,
+ FuncImplHelper>::type {};
+
+} // namespace details
+
+
+#define MM_MEMBER_TRAITS(NAME) details::FunctionTraits
+
+#define MM_MEMBER_ARG_0(NAME)
+#define MM_MEMBER_ARG_1(NAME) typename details::FunctionTraits::ArgT<0> _0
+#define MM_MEMBER_ARG_2(NAME) \
+ MM_MEMBER_ARG_1(NAME), typename details::FunctionTraits::ArgT<1> _1
+#define MM_MEMBER_ARG_3(NAME) \
+ MM_MEMBER_ARG_2(NAME), typename details::FunctionTraits::ArgT<2> _2
+#define MM_MEMBER_ARG_4(NAME) \
+ MM_MEMBER_ARG_3(NAME), typename details::FunctionTraits::ArgT<3> _3
+#define MM_MEMBER_ARG_5(NAME) \
+ MM_MEMBER_ARG_4(NAME), typename details::FunctionTraits::ArgT<4> _4
+#define MM_MEMBER_ARG_6(NAME) \
+ MM_MEMBER_ARG_5(NAME), typename details::FunctionTraits::ArgT<5> _5
+#define MM_MEMBER_ARG_7(NAME) \
+ MM_MEMBER_ARG_6(NAME), typename details::FunctionTraits::ArgT<6> _6
+
+#define MM_MEMBER_ARGNAME_0(NAME)
+#define MM_MEMBER_ARGNAME_1(NAME) _0
+#define MM_MEMBER_ARGNAME_2(NAME) MM_MEMBER_ARGNAME_1(NAME), _1
+#define MM_MEMBER_ARGNAME_3(NAME) MM_MEMBER_ARGNAME_2(NAME), _2
+#define MM_MEMBER_ARGNAME_4(NAME) MM_MEMBER_ARGNAME_3(NAME), _3
+#define MM_MEMBER_ARGNAME_5(NAME) MM_MEMBER_ARGNAME_4(NAME), _4
+#define MM_MEMBER_ARGNAME_6(NAME) MM_MEMBER_ARGNAME_5(NAME), _5
+#define MM_MEMBER_ARGNAME_7(NAME) MM_MEMBER_ARGNAME_6(NAME), _6
+
+#define MM_LINE_NAME(prefix) MM_JOIN_NAME(prefix, __LINE__)
+#define MM_JOIN_NAME(NAME, LINE) MM_JOIN_NAME_1(NAME, LINE)
+#define MM_JOIN_NAME_1(NAME, LINE) NAME##LINE
+
+#define MOCK_FUNCTION_RES_RAW_TYPE(CLASS, NAME, TYPE, ARGS_COUNT, INIT_VALUE, ...) \
+ details::FunctionTraits::ReturnType CLASS::NAME(MM_MEMBER_ARG_##ARGS_COUNT(TYPE)) \
+ __VA_ARGS__ { \
+ return details::FuncImpl::call( \
+ MM_MEMBER_ARGNAME_##ARGS_COUNT(TYPE)); \
+ } \
+ static int MM_LINE_NAME(_init_res_##CLASS##NAME##ARGS_COUNT) = []() { \
+ details::ReturnStaticStorageHelper< \
+ TYPE, COMPTIME_HASH(#CLASS "::" #NAME), \
+ details::FunctionTraits::ReturnType>::setValue(INIT_VALUE); \
+ return 0; \
+ }()
+
+
+/*
+ * Interface
+ */
+
+/*
+ * Defines implementation for the function with specified return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * INIT_VALUE - the new result of the function
+ * ... - the optional const qualifier (must be empty if member function is non-const)
+ */
+#define MOCK_FUNCTION_RES(CLASS, NAME, ARGS_COUNT, INIT_VALUE, ...) \
+ MOCK_FUNCTION_RES_RAW_TYPE(CLASS, NAME, decltype(&CLASS::NAME), ARGS_COUNT, INIT_VALUE, \
+ __VA_ARGS__)
+
+/*
+ * Defines implementation for the function with default-constructed return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * ... - the optional const qualifier (must be empty if member function is non-const)
+ */
+#define MOCK_FUNCTION(CLASS, NAME, ARGS_COUNT, ...) \
+ MOCK_FUNCTION_RES(CLASS, NAME, ARGS_COUNT, \
+ details::DefaultCtorOrNullForVoid< \
+ details::FunctionTraits::ReturnType>::get(), \
+ __VA_ARGS__)
+
+/*
+ * Defines implementation for the overloaded function with specified return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * INIT_VALUE - the new result of the function
+ * ... - the signature of the overloaded function
+ */
+#define MOCK_FUNCTION_OVERLOADED_RES(CLASS, NAME, ARGS_COUNT, INIT_VALUE, ...) \
+ MOCK_FUNCTION_RES_RAW_TYPE(CLASS, NAME, decltype((__VA_ARGS__)&CLASS::NAME), ARGS_COUNT, \
+ INIT_VALUE, )
+
+/*
+ * Defines implementation for the overloaded const member function with specified return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * INIT_VALUE - the new result of the function
+ * ... - the signature of the overloaded function
+ */
+#define MOCK_FUNCTION_OVERLOADED_RES_CONST(CLASS, NAME, ARGS_COUNT, INIT_VALUE, ...) \
+ MOCK_FUNCTION_RES_RAW_TYPE(CLASS, NAME, decltype((__VA_ARGS__)&CLASS::NAME), ARGS_COUNT, \
+ INIT_VALUE, const)
+
+/*
+ * Defines implementation for the overloaded function with default-constructed return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * ... - the signature of the overloaded function
+ */
+#define MOCK_FUNCTION_OVERLOADED(CLASS, NAME, ARGS_COUNT, ...) \
+ MOCK_FUNCTION_OVERLOADED_RES( \
+ CLASS, NAME, ARGS_COUNT, \
+ details::DefaultCtorOrNullForVoid< \
+ details::FunctionTraits::ReturnType>::get(), \
+ __VA_ARGS__)
+
+/*
+ * Defines implementation for the overloaded const member function with default-constructed return value
+ *
+ * CLASS - the class name
+ * NAME - the function name
+ * ARGS_COUNT - the count of function arguments
+ * ... - the signature of the overloaded function
+ */
+#define MOCK_FUNCTION_OVERLOADED_CONST(CLASS, NAME, ARGS_COUNT, ...) \
+ MOCK_FUNCTION_OVERLOADED_RES_CONST( \
+ CLASS, NAME, ARGS_COUNT, \
+ details::DefaultCtorOrNullForVoid< \
+ details::FunctionTraits::ReturnType>::get(), \
+ __VA_ARGS__)
+
+/*
+ * Sets return value for the specified function
+ *
+ * FULL_NAME - the full name of the function (e.g. SomeClass::functionName)
+ * ... - the value to be returned (or arguments to constructor)
+ */
+#define SET_FUNCTION_RESULT(FULL_NAME, ...) \
+ details::ReturnStaticStorageHelper< \
+ decltype(&FULL_NAME), COMPTIME_HASH(#FULL_NAME), \
+ details::FunctionTraits::ReturnType>::setValue(__VA_ARGS__)
+
+/*
+ * Sets return value for the specified overloaded function
+ *
+ * FULL_NAME - the full name of the function (e.g. SomeClass::functionName)
+ * VALUE - the value to be returned
+ * ... - the signature of the overloaded function
+ */
+#define SET_FUNCTION_RESULT_OVERLOADED(FULL_NAME, VALUE, ...) \
+ details::ReturnStaticStorageHelper< \
+ decltype((__VA_ARGS__)&FULL_NAME), COMPTIME_HASH(#FULL_NAME), \
+ details::FunctionTraits::ReturnType>::setValue(VALUE)
+
+/*
+ * Sets implementation for the specified function
+ *
+ * FULL_NAME - the full name of the function (e.g. SomeClass::functionName)
+ * ... - the lambda or function for implementation
+ */
+#define SET_FUNCTION_IMPL(FULL_NAME, ...) \
+ details::FuncImpl::func() = __VA_ARGS__
+
+/*
+ * Sets implementation for the specified overloaded function
+ *
+ * FULL_NAME - the full name of the function (e.g. SomeClass::functionName)
+ * FUNC_TYPE - the signature of the overloaded function (must be alias if it contains commas)
+ * ... - the lambda or function for implementation
+ */
+#define SET_FUNCTION_IMPL_OVERLOADED(FULL_NAME, FUNC_TYPE, ...) \
+ details::FuncImpl::func() = \
+ __VA_ARGS__
+
+#endif // __STATIC_MOCK__H__
diff --git a/src/runtime/kwalletd/autotests/testhelpers.hpp b/src/runtime/kwalletd/autotests/testhelpers.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..56c3c02041302e4e2b277b5a5c5d8c20cc547ff9
--- /dev/null
+++ b/src/runtime/kwalletd/autotests/testhelpers.hpp
@@ -0,0 +1,100 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef __TESTHELPERS_HPP__
+#define __TESTHELPERS_HPP__
+
+#include
+#include
+#include
+#include
+#include <../kwalletfreedesktopservice.h>
+
+template
+using Testset = std::vector>;
+
+template
+struct EasyFormater {
+ std::string operator()(const T& v) const {
+ return std::string(v.toStdString());
+ }
+};
+
+template
+struct EasyFormater::value ||
+ std::is_floating_point::value>::type> {
+ std::string operator()(T v) const {
+ return std::to_string(v);
+ }
+};
+
+template <>
+struct EasyFormater {
+ std::string operator()(const FdoUniqueLabel& v) const {
+ return "{" + v.label.toStdString() + ", " + std::to_string(v.copyId) + "}";
+ }
+};
+
+template <>
+struct EasyFormater {
+ std::string operator()(const EntryLocation& v) const {
+ return "{" + v.folder.toStdString() + ", " + v.key.toStdString() + "}";
+ }
+};
+
+
+template
+struct TestsetCmpHelper {
+ template
+ bool cmp(F&& function, const T& pair) {
+ return function(pair.first) == pair.second;
+ }
+ template
+ std::string format(F&& function, const T& pair) {
+ using RetT = decltype(function(pair.first));
+ return EasyFormater()(pair.first) + " (evaluates to " +
+ EasyFormater()(function(pair.first)) + ") not equal with " +
+ EasyFormater()(pair.second);
+ }
+};
+template <>
+struct TestsetCmpHelper {
+ template
+ bool cmp(F&& function, const T& pair) {
+ return function(pair.second) == pair.first;
+ }
+ template
+ std::string format(F&& function, const T& pair) {
+ using RetT = decltype(function(pair.second));
+ return EasyFormater()(pair.second) + " (evaluates to " +
+ EasyFormater()(function(pair.second)) + ") not equal with " +
+ EasyFormater()(pair.first);
+ }
+};
+
+template
+void runTestsetTmpl(F&& function, const Testset& labelMap) {
+ for (auto& pair : labelMap) {
+ bool ok = TestsetCmpHelper().cmp(function, pair);
+ if (!ok) {
+ std::string str = TestsetCmpHelper().format(function, pair);
+ QVERIFY2(ok, str.c_str());
+ }
+ }
+}
+
+template
+void runTestset(F&& function, const Testset& labelMap) {
+ return runTestsetTmpl(std::forward(function), labelMap);
+}
+
+template
+void runRevTestset(F&& function, const Testset& labelMap) {
+ return runTestsetTmpl(std::forward(function), labelMap);
+}
+
+#endif // __TESTHELPERS_HPP__
+
diff --git a/src/runtime/kwalletd/backend/CMakeLists.txt b/src/runtime/kwalletd/backend/CMakeLists.txt
index 1523b6c0af16a855b2197577a46afe65facd9693..6fbb73349ff68e9c5edfb9c099015d68bb35a35e 100644
--- a/src/runtime/kwalletd/backend/CMakeLists.txt
+++ b/src/runtime/kwalletd/backend/CMakeLists.txt
@@ -18,12 +18,14 @@ find_package(KF5CoreAddons ${KF_DEP_VERSION} REQUIRED)
find_package(KF5I18n ${KF_DEP_VERSION} REQUIRED)
find_package(KF5Notifications ${KF_DEP_VERSION} REQUIRED)
find_package(KF5WidgetsAddons ${KF_DEP_VERSION} REQUIRED)
+find_package(KF5Config ${KF_DEP_VERSION} REQUIRED)
find_package(LibGcrypt 1.5.0 REQUIRED)
set_package_properties(LibGcrypt PROPERTIES
TYPE REQUIRED
PURPOSE "kwalletd needs libgcrypt to perform PBKDF2-SHA512 hashing"
)
+find_package(Qca-qt${QT_MAJOR_VERSION} REQUIRED 2.3.1)
add_library(kwalletbackend5 SHARED)
@@ -56,7 +58,7 @@ generate_export_header(kwalletbackend5)
ecm_setup_version(${KF_VERSION} VARIABLE_PREFIX KWALLETBACKEND SOVERSION 5)
-target_link_libraries(kwalletbackend5 Qt${QT_MAJOR_VERSION}::Widgets KF5::WidgetsAddons KF5::CoreAddons KF5::Notifications KF5::I18n ${LIBGCRYPT_LIBRARIES})
+target_link_libraries(kwalletbackend5 Qt${QT_MAJOR_VERSION}::Widgets KF5::WidgetsAddons KF5::CoreAddons KF5::Notifications KF5::I18n ${LIBGCRYPT_LIBRARIES} ${Qca_LIBRARY})
if(Gpgmepp_FOUND)
target_link_libraries(kwalletbackend5 Gpgmepp)
endif(Gpgmepp_FOUND)
diff --git a/src/runtime/kwalletd/backend/kwalletbackend.cc b/src/runtime/kwalletd/backend/kwalletbackend.cc
index 48131a58f6660fb65c90be034b79a37961a23ca9..e58da6740498610898c60eb5f990a6bdc7dc32b2 100644
--- a/src/runtime/kwalletd/backend/kwalletbackend.cc
+++ b/src/runtime/kwalletd/backend/kwalletbackend.cc
@@ -45,6 +45,22 @@ using namespace KWallet;
#define KWMAGIC "KWALLET\n\r\0\r\n"
+static const QByteArray walletAllowedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^&'@{}[],$=!-#()%.+_\r\n\t\f\v ";
+
+/* The encoding works if the name contains at least one unsupported character.
+ * Names that were allowed prior to the Secret Service API patch remain intact.
+ */
+QString Backend::encodeWalletName(const QString &name) {
+ /* Use a semicolon as "percent" because it does not conflict with already allowed characters for wallet names
+ * and is allowed for file names
+ */
+ return QString::fromUtf8(name.toUtf8().toPercentEncoding(walletAllowedChars, {}, ';'));
+}
+
+QString Backend::decodeWalletName(const QString &encodedName) {
+ return QString::fromUtf8(QByteArray::fromPercentEncoding(encodedName.toUtf8(), ';'));
+}
+
class Backend::BackendPrivate
{
};
@@ -63,7 +79,7 @@ Backend::Backend(const QString &name, bool isPath)
if (isPath) {
_path = name;
} else {
- _path = getSaveLocation() + QDir::separator() + _name + ".kwl";
+ _path = getSaveLocation() + '/' + encodeWalletName(_name) + ".kwl";
}
_open = false;
@@ -236,7 +252,7 @@ int Backend::deref()
bool Backend::exists(const QString &wallet)
{
QString saveLocation = getSaveLocation();
- QString path = saveLocation + '/' + wallet + QLatin1String(".kwl");
+ QString path = saveLocation + '/' + encodeWalletName(wallet) + QLatin1String(".kwl");
// Note: 60 bytes is presently the minimum size of a wallet file.
// Anything smaller is junk.
return QFile::exists(path) && QFileInfo(path).size() >= 60;
@@ -449,7 +465,7 @@ int Backend::sync(WId w)
return rc;
}
-int Backend::close(bool save)
+int Backend::closeInternal(bool save)
{
// save if requested
if (save) {
@@ -466,13 +482,21 @@ int Backend::close(bool save)
}
}
_entries.clear();
+ _open = false;
+
+ return 0;
+}
+
+int Backend::close(bool save)
+{
+ int rc = closeInternal(save);
+ if (rc)
+ return rc;
// empty the password hash
_passhash.fill(0);
_newPassHash.fill(0);
- _open = false;
-
return 0;
}
@@ -481,6 +505,45 @@ const QString &Backend::walletName() const
return _name;
}
+int Backend::renameWallet(const QString &newName, bool isPath)
+{
+ QString newPath;
+ const auto saveLocation = getSaveLocation();
+
+ if (isPath) {
+ newPath = newName;
+ } else {
+ newPath = saveLocation + QChar::fromLatin1('/') + encodeWalletName(newName) + QStringLiteral(".kwl");
+ }
+
+ if (newPath == _path) {
+ return 0;
+ }
+
+ if (QFile::exists(newPath)) {
+ return -EEXIST;
+ }
+
+ int rc = closeInternal(true);
+ if (rc) {
+ return rc;
+ }
+
+ QFile::rename(_path, newPath);
+ QFile::rename(saveLocation + QChar::fromLatin1('/') + encodeWalletName(_name) + QStringLiteral(".salt"),
+ saveLocation + QChar::fromLatin1('/') + encodeWalletName(newName) + QStringLiteral(".salt"));
+
+ _name = newName;
+ _path = newPath;
+
+ rc = openInternal();
+ if (rc) {
+ return rc;
+ }
+
+ return 0;
+}
+
bool Backend::isOpen() const
{
return _open;
@@ -700,7 +763,7 @@ void Backend::setPassword(const QByteArray &password)
password2hash(password, _passhash);
QByteArray salt;
- QFile saltFile(getSaveLocation() + QDir::separator() + _name + ".salt");
+ QFile saltFile(getSaveLocation() + '/' + encodeWalletName(_name) + ".salt");
if (!saltFile.exists() || saltFile.size() == 0) {
salt = createAndSaveSalt(saltFile.fileName());
} else {
diff --git a/src/runtime/kwalletd/backend/kwalletbackend.h b/src/runtime/kwalletd/backend/kwalletbackend.h
index 20121c54f29871b4293a158a1a445ae61875c340..9dffa3b1df582156c5cdd4b28be1d058823166bf 100644
--- a/src/runtime/kwalletd/backend/kwalletbackend.h
+++ b/src/runtime/kwalletd/backend/kwalletbackend.h
@@ -99,6 +99,9 @@ public:
// Returns the current wallet name.
const QString &walletName() const;
+ // Rename the wallet
+ int renameWallet(const QString &newName, bool isPath = false);
+
// The list of folders.
QStringList folderList() const;
@@ -189,12 +192,14 @@ public:
#endif
static QString getSaveLocation();
+ static QString encodeWalletName(const QString &name);
+ static QString decodeWalletName(const QString &encodedName);
private:
Q_DISABLE_COPY(Backend)
class BackendPrivate;
BackendPrivate *const d;
- const QString _name;
+ QString _name;
QString _path;
bool _open;
bool _useNewHash = false;
@@ -209,6 +214,7 @@ private:
QByteArray _passhash; // password hash used for saving the wallet
QByteArray _newPassHash; // Modern hash using KWALLET_HASH_PBKDF2_SHA512
BackendCipherType _cipherType; // the kind of encryption used for this wallet
+
#ifdef HAVE_GPGMEPP
GpgME::Key _gpgKey;
#endif
@@ -218,6 +224,7 @@ private:
// open the wallet with the password already set. This is
// called internally by both open and openPreHashed.
int openInternal(WId w = 0);
+ int closeInternal(bool save);
void swapToNewHash();
QByteArray createAndSaveSalt(const QString &path) const;
};
diff --git a/src/runtime/kwalletd/kwalletd.cpp b/src/runtime/kwalletd/kwalletd.cpp
index 36098b2eb8953258fea6e81bf44333644ab8eef0..b00c1a896a1bf70aca8ee301c86129bc54bf772d 100644
--- a/src/runtime/kwalletd/kwalletd.cpp
+++ b/src/runtime/kwalletd/kwalletd.cpp
@@ -10,6 +10,11 @@
#include "kwalletd_debug.h"
#include "kbetterthankdialog.h"
+#include "kwalletfreedesktopcollection.h"
+#include "kwalletfreedesktopitem.h"
+#include "kwalletfreedesktopprompt.h"
+#include "kwalletfreedesktopservice.h"
+#include "kwalletfreedesktopsession.h"
#include "kwalletwizard.h"
#ifdef HAVE_GPGMEPP
@@ -78,6 +83,11 @@ public:
}
}
+ static int getTransactionId()
+ {
+ return nextTransactionId;
+ }
+
~KWalletTransaction()
{
}
@@ -126,10 +136,17 @@ KWalletD::KWalletD()
connect(&_closeTimers, &KTimeout::timedOut, this, &KWalletD::timedOutClose);
connect(&_syncTimers, &KTimeout::timedOut, this, &KWalletD::timedOutSync);
- (void)new KWalletAdaptor(this);
- // register services
- QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwalletd5"));
- QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kwalletd5"), this);
+ KConfig kwalletrc(QStringLiteral("kwalletrc"));
+ KConfigGroup cfgWallet(&kwalletrc, "Wallet");
+ KConfigGroup cfgSecrets(&kwalletrc, "org.freedesktop.secrets");
+
+ if (cfgWallet.readEntry("apiEnabled", true)) {
+ (void)new KWalletAdaptor(this);
+
+ // register services
+ QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kwalletd5"));
+ QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kwalletd5"), this);
+ }
#ifdef Q_WS_X11
screensaver = 0;
@@ -148,6 +165,13 @@ KWalletD::KWalletD()
_serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
connect(&_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &KWalletD::slotServiceOwnerChanged);
+
+ if (cfgSecrets.readEntry("apiEnabled", true)) {
+ _fdoService.reset(new KWalletFreedesktopService(this));
+ } else {
+ /* Do not keep dbus-daemon waiting for the org.freedesktop.secrets by registering the dummy-service */
+ KWalletFreedesktopService(nullptr);
+ }
}
void KWalletD::registerKWalletd4Service()
@@ -173,6 +197,16 @@ KWalletD::~KWalletD()
qDeleteAll(_transactions);
}
+QString KWalletD::encodeWalletName(const QString &name)
+{
+ return KWallet::Backend::encodeWalletName(name);
+}
+
+QString KWalletD::decodeWalletName(const QString &mangledName)
+{
+ return KWallet::Backend::decodeWalletName(mangledName);
+}
+
#ifdef Q_WS_X11
void KWalletD::connectToScreenSaver()
{
@@ -212,7 +246,6 @@ QPair KWalletD::findWallet(const QString &walletName) c
return qMakePair(-1, static_cast(nullptr));
}
-static const QRegularExpression walletRegex(QStringLiteral("^[\\w\\^\\&\\'\\@\\{\\}\\[\\]\\,\\$\\=\\!\\-\\#\\(\\)\\%\\.\\+\\_\\s]+$"));
bool KWalletD::_processing = false;
void KWalletD::processTransactions()
@@ -318,10 +351,6 @@ int KWalletD::open(const QString &wallet, qlonglong wId, const QString &appid)
return -1;
}
- if (!walletRegex.match(wallet).hasMatch()) {
- return -1;
- }
-
KWalletTransaction *xact = new KWalletTransaction(connection());
_transactions.append(xact);
@@ -342,17 +371,23 @@ int KWalletD::open(const QString &wallet, qlonglong wId, const QString &appid)
return 0;
}
-int KWalletD::openAsync(const QString &wallet, qlonglong wId, const QString &appid, bool handleSession)
+int KWalletD::nextTransactionId() const
{
- if (!_enabled) { // guard
- return -1;
- }
+ return KWalletTransaction::getTransactionId();
+}
- if (!walletRegex.match(wallet).hasMatch()) {
+int KWalletD::openAsync(const QString &wallet,
+ qlonglong wId,
+ const QString &appid,
+ bool handleSession,
+ const QDBusConnection &connection,
+ const QDBusMessage &message)
+{
+ if (!_enabled) { // guard
return -1;
}
- KWalletTransaction *xact = new KWalletTransaction(connection());
+ KWalletTransaction *xact = new KWalletTransaction(connection);
_transactions.append(xact);
xact->appid = appid;
@@ -362,10 +397,10 @@ int KWalletD::openAsync(const QString &wallet, qlonglong wId, const QString &app
xact->tType = KWalletTransaction::Open;
xact->isPath = false;
if (handleSession) {
- qCDebug(KWALLETD_LOG) << "openAsync for " << message().service();
- _serviceWatcher.setConnection(connection());
- _serviceWatcher.addWatchedService(message().service());
- xact->service = message().service();
+ qCDebug(KWALLETD_LOG) << "openAsync for " << message.service();
+ _serviceWatcher.setConnection(connection);
+ _serviceWatcher.addWatchedService(message.service());
+ xact->service = message.service();
}
QTimer::singleShot(0, this, SLOT(processTransactions()));
checkActiveDialog();
@@ -373,6 +408,11 @@ int KWalletD::openAsync(const QString &wallet, qlonglong wId, const QString &app
return xact->tId;
}
+int KWalletD::openAsync(const QString &wallet, qlonglong wId, const QString &appid, bool handleSession)
+{
+ return openAsync(wallet, wId, appid, handleSession, connection(), message());
+}
+
int KWalletD::openPathAsync(const QString &path, qlonglong wId, const QString &appid, bool handleSession)
{
if (!_enabled) { // guard
@@ -856,8 +896,8 @@ bool KWalletD::isAuthorizedApp(const QString &appid, const QString &wallet, WId
int KWalletD::deleteWallet(const QString &wallet)
{
int result = -1;
- QString path = KWallet::Backend::getSaveLocation() + "/" + wallet + ".kwl";
- QString pathSalt = KWallet::Backend::getSaveLocation() + "/" + wallet + ".salt";
+ QString path = KWallet::Backend::getSaveLocation() + "/" + encodeWalletName(wallet) + ".kwl";
+ QString pathSalt = KWallet::Backend::getSaveLocation() + "/" + encodeWalletName(wallet) + ".salt";
if (QFile::exists(path)) {
const QPair walletInfo = findWallet(wallet);
@@ -1010,14 +1050,14 @@ int KWalletD::internalClose(KWallet::Backend *const w, const int handle, const b
return -1;
}
-int KWalletD::close(int handle, bool force, const QString &appid)
+int KWalletD::close(int handle, bool force, const QString &appid, const QDBusMessage &message)
{
KWallet::Backend *w = _wallets.value(handle);
if (w) {
if (_sessions.hasSession(appid, handle)) {
// remove one handle for the application
- bool removed = _sessions.removeSession(appid, message().service(), handle);
+ bool removed = _sessions.removeSession(appid, message.service(), handle);
// alternatively try sessionless
if (removed || _sessions.removeSession(appid, QLatin1String(""), handle)) {
w->deref();
@@ -1029,6 +1069,11 @@ int KWalletD::close(int handle, bool force, const QString &appid)
return -1; // not open to begin with, or other error
}
+int KWalletD::close(int handle, bool force, const QString &appid)
+{
+ return close(handle, force, appid, message());
+}
+
bool KWalletD::isOpen(const QString &wallet)
{
const QPair walletInfo = findWallet(wallet);
@@ -1067,7 +1112,7 @@ QStringList KWalletD::wallets() const
if (fn.endsWith(QLatin1String(".kwl"))) {
fn.truncate(fn.length() - 4);
}
- rc += fn;
+ rc += decodeWalletName(fn);
}
return rc;
}
@@ -1347,13 +1392,14 @@ int KWalletD::writeMap(int handle, const QString &folder, const QString &key, co
b->writeEntry(&e);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
+ emitEntryUpdated(b->walletName(), folder, key);
return 0;
}
return -1;
}
-int KWalletD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, int entryType, const QString &appid)
+int KWalletD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, const QString &appid)
{
KWallet::Backend *b;
@@ -1362,17 +1408,18 @@ int KWalletD::writeEntry(int handle, const QString &folder, const QString &key,
KWallet::Entry e;
e.setKey(key);
e.setValue(value);
- e.setType(KWallet::Wallet::EntryType(entryType));
+ e.setType(KWallet::Wallet::Stream);
b->writeEntry(&e);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
+ emitEntryUpdated(b->walletName(), folder, key);
return 0;
}
return -1;
}
-int KWalletD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, const QString &appid)
+int KWalletD::writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, int entryType, const QString &appid)
{
KWallet::Backend *b;
@@ -1381,7 +1428,7 @@ int KWalletD::writeEntry(int handle, const QString &folder, const QString &key,
KWallet::Entry e;
e.setKey(key);
e.setValue(value);
- e.setType(KWallet::Wallet::Stream);
+ e.setType(KWallet::Wallet::EntryType(entryType));
b->writeEntry(&e);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
@@ -1404,6 +1451,7 @@ int KWalletD::writePassword(int handle, const QString &folder, const QString &ke
b->writeEntry(&e);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
+ emitEntryUpdated(b->walletName(), folder, key);
return 0;
}
@@ -1454,6 +1502,7 @@ int KWalletD::removeEntry(int handle, const QString &folder, const QString &key,
bool rc = b->removeEntry(key);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
+ emitEntryDeleted(b->walletName(), folder, key);
return rc ? 0 : -3;
}
@@ -1567,12 +1616,19 @@ int KWalletD::renameEntry(int handle, const QString &folder, const QString &oldN
int rc = b->renameEntry(oldName, newName);
initiateSync(handle);
emitFolderUpdated(b->walletName(), folder);
+ emitEntryRenamed(b->walletName(), folder, oldName, newName);
return rc;
}
return -1;
}
+int KWalletD::renameWallet(const QString &oldName, const QString &newName)
+{
+ const QPair walletInfo = findWallet(oldName);
+ return walletInfo.second->renameWallet(newName);
+}
+
QStringList KWalletD::users(const QString &wallet) const
{
const QPair walletInfo = findWallet(wallet);
@@ -1605,6 +1661,21 @@ void KWalletD::emitFolderUpdated(const QString &wallet, const QString &folder)
Q_EMIT folderUpdated(wallet, folder);
}
+void KWalletD::emitEntryUpdated(const QString &wallet, const QString &folder, const QString &key)
+{
+ Q_EMIT entryUpdated(wallet, folder, key);
+}
+
+void KWalletD::emitEntryRenamed(const QString &wallet, const QString &folder, const QString &oldName, const QString &newName)
+{
+ Q_EMIT entryRenamed(wallet, folder, oldName, newName);
+}
+
+void KWalletD::emitEntryDeleted(const QString &wallet, const QString &folder, const QString &key)
+{
+ Q_EMIT entryDeleted(wallet, folder, key);
+}
+
void KWalletD::emitWalletListDirty()
{
const QStringList walletsInDisk = wallets();
@@ -1796,10 +1867,6 @@ int KWalletD::pamOpen(const QString &wallet, const QByteArray &passwordHash, int
return -1;
}
- if (!walletRegex.match(wallet).hasMatch()) {
- return -1;
- }
-
// check if the wallet is already open
QPair walletInfo = findWallet(wallet);
int rc = walletInfo.first;
@@ -1847,4 +1914,5 @@ int KWalletD::pamOpen(const QString &wallet, const QByteArray &passwordHash, int
return handle;
}
+
// vim: tw=220:ts=4
diff --git a/src/runtime/kwalletd/kwalletd.h b/src/runtime/kwalletd/kwalletd.h
index 8f82a0ca3d0036b8143e19240cf36c5570ed5a78..7638a1a55519cfcecbbb5c3ec0918671c1ecf909 100644
--- a/src/runtime/kwalletd/kwalletd.h
+++ b/src/runtime/kwalletd/kwalletd.h
@@ -27,6 +27,7 @@ class KTimeout;
// @Private
class KWalletTransaction;
class KWalletSessionStore;
+class KWalletFreedesktopService;
class KWalletD : public QObject, protected QDBusContext
{
@@ -36,7 +37,17 @@ public:
KWalletD();
~KWalletD() override;
+ static QString encodeWalletName(const QString &name);
+ static QString decodeWalletName(const QString &mangledName);
+
+ int nextTransactionId() const;
+ int
+ openAsync(const QString &wallet, qlonglong wId, const QString &appid, bool handleSession, const QDBusConnection &connection, const QDBusMessage &message);
+ // Close and lock the wallet
+ // Accepts "message" for working from other QDBusContexts
+ int close(int handle, bool force, const QString &appid, const QDBusMessage &message);
public Q_SLOTS:
+
// Is the wallet enabled? If not, all open() calls fail.
bool isEnabled() const;
@@ -121,6 +132,8 @@ public Q_SLOTS:
// Rename an entry. rc=0 on success.
int renameEntry(int handle, const QString &folder, const QString &oldName, const QString &newName, const QString &appid);
+ // Rename the wallet
+ int renameWallet(const QString &oldName, const QString &newName);
// Write an entry. rc=0 on success.
int writeEntry(int handle, const QString &folder, const QString &key, const QByteArray &value, int entryType, const QString &appid);
@@ -176,6 +189,9 @@ Q_SIGNALS:
void allWalletsClosed();
void folderListUpdated(const QString &wallet);
void folderUpdated(const QString &, const QString &);
+ void entryUpdated(const QString &, const QString &, const QString &);
+ void entryRenamed(const QString &, const QString &, const QString &, const QString &);
+ void entryDeleted(const QString &, const QString &, const QString &);
void applicationDisconnected(const QString &wallet, const QString &application);
private Q_SLOTS:
@@ -205,6 +221,9 @@ private:
// Emit signals about closing wallets
void doCloseSignals(int, const QString &);
void emitFolderUpdated(const QString &, const QString &);
+ void emitEntryUpdated(const QString &, const QString &, const QString &);
+ void emitEntryRenamed(const QString &, const QString &, const QString &, const QString &);
+ void emitEntryDeleted(const QString &, const QString &, const QString &);
// Implicitly allow access for this application
bool implicitAllow(const QString &wallet, const QString &app);
bool implicitDeny(const QString &wallet, const QString &app);
@@ -246,6 +265,8 @@ private:
KWalletSessionStore _sessions;
QDBusServiceWatcher _serviceWatcher;
+ std::unique_ptr _fdoService;
+
bool _useGpg;
};
diff --git a/src/runtime/kwalletd/kwalletdbuscontext.cpp b/src/runtime/kwalletd/kwalletdbuscontext.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f764458668ba81e658d75926a776c053a255745
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletdbuscontext.cpp
@@ -0,0 +1,17 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2022 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletdbuscontext.h"
+
+const QDBusMessage &KWalletDBusContext::message() const
+{
+ return QDBusContext::message();
+}
+
+QDBusConnection KWalletDBusContext::connection() const
+{
+ return QDBusContext::connection();
+}
diff --git a/src/runtime/kwalletd/kwalletdbuscontext.h b/src/runtime/kwalletd/kwalletdbuscontext.h
new file mode 100644
index 0000000000000000000000000000000000000000..87574f06173e99bd28797efb0e36680fd3e13187
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletdbuscontext.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2022 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETDBUSCONTEXT_H_
+#define _KWALLETDBUSCONTEXT_H_
+
+#include "qdbuserror.h"
+#include
+#include
+#include
+
+/* FDO_DBUS_CONTEXT macro will be replaced by KWalletDBusContextDummy during
+ * preprocessing if FDO_ENABLE_DUMMY_MESSAGE_CONNECTION was defined,
+ * otherwise we get QDBusContext.
+ *
+ * This is used for mocking QDBusContext in autotests.
+ *
+ * QDBusContext's connection() and message() member functions can't be called
+ * without a real connection context (this cause segfault).
+ * So we need to use KWalletDBusContextDummy in case some DBus-related
+ * member functions may call connection()/message().
+ *
+ * This header defines FDO_DBUS_CONTEXT macro that should be used instead of
+ * QDBusContext in all DBus-related which we want to use in autotests.
+ */
+
+#ifdef FDO_ENABLE_DUMMY_MESSAGE_CONNECTION
+
+class KWalletDBusContextDummy : public QDBusContext
+{
+public:
+ const QDBusMessage &message()
+ {
+ static auto msg = QDBusMessage::createSignal(QStringLiteral("dummy"), QStringLiteral("dummy"), QStringLiteral("dummy"));
+ return msg;
+ }
+ QDBusConnection connection() const
+ {
+ return QDBusConnection::sessionBus();
+ }
+};
+
+#define FDO_DBUS_CONTEXT KWalletDBusContextDummy
+
+#else
+
+#define FDO_DBUS_CONTEXT QDBusContext
+
+#endif
+
+#endif // _KWALLETDBUSCONTEXT_H_
diff --git a/src/runtime/kwalletd/kwalletfreedesktopattributes.cpp b/src/runtime/kwalletd/kwalletfreedesktopattributes.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e1cba15cc096762b17d818c5ccdaaa6e2eb6d832
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopattributes.cpp
@@ -0,0 +1,346 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopattributes.h"
+
+#include "kwalletd.h"
+#include "kwalletd_debug.h"
+#include "kwalletfreedesktopcollection.h"
+#include
+#include
+#include
+
+KWalletFreedesktopAttributes::KWalletFreedesktopAttributes(const QString &walletName)
+{
+ QString writeLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+ if (!writeLocation.isEmpty() && writeLocation.back() == QChar::fromLatin1('5')) {
+ writeLocation.resize(writeLocation.size() - 1);
+ }
+ _path = writeLocation + QChar::fromLatin1('/') + KWalletD::encodeWalletName(walletName) + QStringLiteral("_attributes.json");
+
+ read();
+
+ if (!_params.contains(FDO_KEY_CREATED)) {
+ const auto currentTime = QString::number(QDateTime::currentSecsSinceEpoch());
+ _params[FDO_KEY_CREATED] = currentTime;
+ _params[FDO_KEY_MODIFIED] = currentTime;
+ }
+}
+
+void KWalletFreedesktopAttributes::read()
+{
+ QByteArray content;
+ {
+ QFile file(_path);
+ file.open(QIODevice::ReadOnly | QIODevice::Text);
+ if (!file.isOpen()) {
+ qCDebug(KWALLETD_LOG) << "Can't read attributes file " << _path;
+ return;
+ }
+ content = file.readAll();
+ }
+
+ const auto jsonDoc = QJsonDocument::fromJson(content);
+ if (jsonDoc.isObject()) {
+ _params = jsonDoc.object();
+ } else {
+ qCWarning(KWALLETD_LOG) << "Can't read attributes: the root element must be an JSON-object: " << _path;
+ _params = QJsonObject();
+ }
+}
+
+void KWalletFreedesktopAttributes::write()
+{
+ if (_params.empty()) {
+ QFile::remove(_path);
+ return;
+ }
+
+ updateLastModified();
+
+ QSaveFile sf(_path);
+ if (!sf.open(QIODevice::WriteOnly | QIODevice::Unbuffered)) {
+ qCWarning(KWALLETD_LOG) << "Can't write attributes file: " << _path;
+ return;
+ }
+ sf.setPermissions(QSaveFile::ReadUser | QSaveFile::WriteUser);
+
+ const QJsonDocument saveDoc(_params);
+
+ const QByteArray jsonBytes = saveDoc.toJson();
+ if (sf.write(jsonBytes) != jsonBytes.size()) {
+ sf.cancelWriting();
+ qCWarning(KWALLETD_LOG) << "Cannot write attributes file " << _path;
+ return;
+ }
+ if (!sf.commit()) {
+ qCWarning(KWALLETD_LOG) << "Cannot commit attributes file " << _path;
+ }
+}
+
+static QString entryLocationToStr(const EntryLocation &entryLocation)
+{
+ return entryLocation.folder + QChar::fromLatin1('/') + entryLocation.key;
+}
+
+static EntryLocation splitToEntryLocation(const QString &entryLocation)
+{
+ const int slashPos = entryLocation.indexOf(QChar::fromLatin1('/'));
+ if (slashPos == -1) {
+ qCWarning(KWALLETD_LOG) << "Entry location '" << entryLocation << "' has no slash '/'";
+ return {};
+ } else {
+ return {entryLocation.left(slashPos), entryLocation.right((entryLocation.size() - slashPos) - 1)};
+ }
+}
+
+void KWalletFreedesktopAttributes::remove(const EntryLocation &entryLocation)
+{
+ _params.remove(entryLocationToStr(entryLocation));
+ if (_params.empty()) {
+ QFile::remove(_path);
+ } else {
+ write();
+ }
+}
+
+void KWalletFreedesktopAttributes::deleteFile()
+{
+ QFile::remove(_path);
+}
+
+void KWalletFreedesktopAttributes::renameLabel(const EntryLocation &oldLocation, const EntryLocation &newLocation)
+{
+ const QString oldLoc = entryLocationToStr(oldLocation);
+
+ const auto found = _params.find(oldLoc);
+ if (found == _params.end() || !found->isObject()) {
+ qCWarning(KWALLETD_LOG) << "Can't rename label (!?)";
+ return;
+ }
+ const auto obj = found->toObject();
+ _params.erase(found);
+ _params.insert(entryLocationToStr(newLocation), obj);
+
+ write();
+}
+
+void KWalletFreedesktopAttributes::renameWallet(const QString &newName)
+{
+ QString writeLocation = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
+ if (!writeLocation.isEmpty() && writeLocation.back() == QChar::fromLatin1('5')) {
+ writeLocation.resize(writeLocation.size() - 1);
+ }
+ const QString newPath = writeLocation + QChar::fromLatin1('/') + newName + QStringLiteral("_attributes.json");
+
+ QFile::rename(_path, newPath);
+ _path = newPath;
+}
+
+void KWalletFreedesktopAttributes::newItem(const EntryLocation &entryLocation)
+{
+ _params[entryLocationToStr(entryLocation)] = QJsonObject();
+}
+
+QList KWalletFreedesktopAttributes::matchAttributes(const StrStrMap &attributes) const
+{
+ QList items;
+
+ for (auto i = _params.constBegin(); i != _params.constEnd(); ++i) {
+ if (!i->isObject()) {
+ continue;
+ }
+
+ bool match = true;
+ const auto itemParams = i->toObject();
+ const auto foundItemAttribs = itemParams.find(QStringLiteral("attributes"));
+ if (foundItemAttribs == itemParams.end() || !foundItemAttribs->isObject()) {
+ continue;
+ }
+ const auto itemAttribs = foundItemAttribs->toObject();
+
+ for (auto i = attributes.constBegin(); i != attributes.constEnd(); ++i) {
+ const auto foundKey = itemAttribs.find(i.key());
+ if (foundKey == itemAttribs.end() || !foundKey->isString() || foundKey->toString() != i.value()) {
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ items += splitToEntryLocation(i.key());
+ }
+ }
+
+ return items;
+}
+
+void KWalletFreedesktopAttributes::setAttributes(const EntryLocation &entryLocation, const StrStrMap &attributes)
+{
+ QJsonObject jsonAttrs;
+ for (auto i = attributes.constBegin(); i != attributes.constEnd(); ++i) {
+ jsonAttrs.insert(i.key(), i.value());
+ }
+
+ const QString strLocation = entryLocationToStr(entryLocation);
+
+ const auto foundParams = _params.find(strLocation);
+ QJsonObject params;
+ if (foundParams != _params.end() && foundParams->isObject()) {
+ params = foundParams->toObject();
+ } else {
+ return;
+ }
+
+ if (jsonAttrs.empty()) {
+ params.remove(QStringLiteral("attributes"));
+ } else {
+ params[QStringLiteral("attributes")] = jsonAttrs;
+ }
+
+ _params[strLocation] = params;
+
+ write();
+}
+
+StrStrMap KWalletFreedesktopAttributes::getAttributes(const EntryLocation &entryLocation) const
+{
+ const auto foundObj = _params.find(entryLocationToStr(entryLocation));
+ if (foundObj == _params.end() || !foundObj->isObject()) {
+ return StrStrMap();
+ }
+ const auto jsonParams = foundObj->toObject();
+
+ const auto foundAttrs = jsonParams.find(QStringLiteral("attributes"));
+ if (foundAttrs == jsonParams.end() || !foundAttrs->isObject()) {
+ return StrStrMap();
+ }
+ const auto jsonAttrs = foundAttrs->toObject();
+
+ StrStrMap itemAttrs;
+
+ for (auto i = jsonAttrs.constBegin(); i != jsonAttrs.constEnd(); ++i) {
+ if (i.value().isString()) {
+ itemAttrs.insert(i.key(), i.value().toString());
+ }
+ }
+
+ return itemAttrs;
+}
+
+QString KWalletFreedesktopAttributes::getStringParam(const EntryLocation &entryLocation, const QString ¶mName, const QString &defaultParam) const
+{
+ const auto foundParams = _params.find(entryLocationToStr(entryLocation));
+ if (foundParams == _params.end() || !foundParams->isObject()) {
+ return defaultParam;
+ }
+ const auto params = foundParams->toObject();
+
+ const auto foundParam = params.find(paramName);
+ if (foundParam == params.end() || !foundParam->isString()) {
+ return defaultParam;
+ }
+
+ return foundParam->toString();
+}
+
+qulonglong KWalletFreedesktopAttributes::getULongLongParam(const EntryLocation &entryLocation, const QString ¶mName, qulonglong defaultParam) const
+{
+ const auto str = getStringParam(entryLocation, paramName, QString::number(defaultParam));
+ bool ok = false;
+ const auto result = str.toULongLong(&ok);
+ return ok ? result : defaultParam;
+}
+
+void KWalletFreedesktopAttributes::setParam(const EntryLocation &entryLocation, const QString ¶mName, const QString ¶m)
+{
+ const auto entryLoc = entryLocationToStr(entryLocation);
+ const auto foundParams = _params.find(entryLoc);
+ if (foundParams == _params.end() || !foundParams->isObject()) {
+ return;
+ }
+
+ auto params = foundParams->toObject();
+
+ params[paramName] = param;
+ _params[entryLoc] = params;
+
+ write();
+}
+
+void KWalletFreedesktopAttributes::setParam(const EntryLocation &entryLocation, const QString ¶mName, qulonglong param)
+{
+ setParam(entryLocation, paramName, QString::number(param));
+}
+
+void KWalletFreedesktopAttributes::remove(const FdoUniqueLabel &itemUniqLabel)
+{
+ remove(itemUniqLabel.toEntryLocation());
+}
+
+void KWalletFreedesktopAttributes::setAttributes(const FdoUniqueLabel &itemUniqLabel, const StrStrMap &attributes)
+{
+ setAttributes(itemUniqLabel.toEntryLocation(), attributes);
+}
+
+StrStrMap KWalletFreedesktopAttributes::getAttributes(const FdoUniqueLabel &itemUniqLabel) const
+{
+ return getAttributes(itemUniqLabel.toEntryLocation());
+}
+
+QString KWalletFreedesktopAttributes::getStringParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, const QString &defaultParam) const
+{
+ return getStringParam(itemUniqLabel.toEntryLocation(), paramName, defaultParam);
+}
+
+qulonglong KWalletFreedesktopAttributes::getULongLongParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, qulonglong defaultParam) const
+{
+ return getULongLongParam(itemUniqLabel.toEntryLocation(), paramName, defaultParam);
+}
+
+void KWalletFreedesktopAttributes::setParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, const QString ¶m)
+{
+ setParam(itemUniqLabel.toEntryLocation(), paramName, param);
+}
+
+void KWalletFreedesktopAttributes::setParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, qulonglong param)
+{
+ setParam(itemUniqLabel.toEntryLocation(), paramName, param);
+}
+
+QList KWalletFreedesktopAttributes::listItems() const
+{
+ QList items;
+ for (auto i = _params.constBegin(); i != _params.constEnd(); ++i) {
+ if (i->isObject()) {
+ items.push_back(splitToEntryLocation(i.key()));
+ }
+ }
+ return items;
+}
+
+qulonglong KWalletFreedesktopAttributes::lastModified() const
+{
+ auto found = _params.constFind(FDO_KEY_MODIFIED);
+ if (found == _params.constEnd()) {
+ return 0;
+ }
+ return found->toString().toULongLong();
+}
+
+qulonglong KWalletFreedesktopAttributes::birthTime() const
+{
+ auto found = _params.constFind(FDO_KEY_CREATED);
+ if (found == _params.constEnd()) {
+ return 0;
+ }
+ return found->toString().toULongLong();
+}
+
+void KWalletFreedesktopAttributes::updateLastModified()
+{
+ _params[FDO_KEY_MODIFIED] = QString::number(QDateTime::currentSecsSinceEpoch());
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopattributes.h b/src/runtime/kwalletd/kwalletfreedesktopattributes.h
new file mode 100644
index 0000000000000000000000000000000000000000..f273b5fdf530a4c79b05483874259f4222476e20
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopattributes.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPATTRIBUTES_H_
+#define _KWALLETFREEDESKTOPATTRIBUTES_H_
+
+#include "kwalletfreedesktopservice.h"
+
+class KWalletFreedesktopAttributes : public QObject
+{
+public:
+ KWalletFreedesktopAttributes(const QString &walletName);
+
+ void read();
+ void write();
+ void remove(const EntryLocation &entryLocation);
+ void remove(const FdoUniqueLabel &itemUniqLabel);
+ void renameLabel(const EntryLocation &oldLocation, const EntryLocation &newLocation);
+ void deleteFile();
+ void renameWallet(const QString &newName);
+ void newItem(const EntryLocation &entryLocation);
+
+ QList matchAttributes(const StrStrMap &attributes) const;
+ void setAttributes(const EntryLocation &entryLocation, const StrStrMap &attributes);
+ StrStrMap getAttributes(const EntryLocation &entryLocation) const;
+
+ void setAttributes(const FdoUniqueLabel &itemUniqLabel, const StrStrMap &attributes);
+ StrStrMap getAttributes(const FdoUniqueLabel &itemUniqLabel) const;
+
+ QString getStringParam(const EntryLocation &entryLocation, const QString ¶mName, const QString &defaultParam) const;
+ qulonglong getULongLongParam(const EntryLocation &entryLocation, const QString ¶mName, qulonglong defaultParam) const;
+
+ QString getStringParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, const QString &defaultParam) const;
+ qulonglong getULongLongParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, qulonglong defaultParam) const;
+
+ void setParam(const EntryLocation &entryLocation, const QString ¶mName, const QString ¶m);
+ void setParam(const EntryLocation &entryLocation, const QString ¶mName, qulonglong param);
+
+ void setParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, const QString ¶m);
+ void setParam(const FdoUniqueLabel &itemUniqLabel, const QString ¶mName, qulonglong param);
+
+ qulonglong lastModified() const;
+ qulonglong birthTime() const;
+ void updateLastModified();
+
+ QList listItems() const;
+
+private:
+ QString _path;
+ QJsonObject _params;
+};
+
+#endif
diff --git a/src/runtime/kwalletd/kwalletfreedesktopcollection.cpp b/src/runtime/kwalletd/kwalletfreedesktopcollection.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..74bc1c5dff71a0107e6890b4b759b80a179f8698
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopcollection.cpp
@@ -0,0 +1,416 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopcollection.h"
+
+#include "kwalletd.h"
+#include "kwalletfreedesktopcollectionadaptor.h"
+#include "kwalletfreedesktopitem.h"
+
+KWalletFreedesktopCollection::KWalletFreedesktopCollection(KWalletFreedesktopService *service,
+ int handle,
+ const QString &walletName,
+ QDBusObjectPath objectPath)
+ : m_service(service)
+ , m_handle(handle)
+ , m_uniqueLabel(FdoUniqueLabel::fromName(walletName))
+ , m_objectPath(std::move(objectPath))
+ , m_itemAttribs(walletName)
+{
+ (void)new KWalletFreedesktopCollectionAdaptor(this);
+ QDBusConnection::sessionBus().registerObject(fdoObjectPath().path(), this);
+
+ const QStringList aliases = fdoService()->readAliasesFor(walletName);
+ for (const auto &alias : aliases) {
+ QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, this);
+ }
+
+ onWalletChangeState(handle);
+
+ /* Create items described in the attributes file */
+ if (m_handle == -1) {
+ const auto items = itemAttributes().listItems();
+ for (const auto &entryLocation : items) {
+ if (!findItemByEntryLocation(entryLocation)) {
+ pushNewItem(entryLocation.toUniqueLabel(), nextItemPath());
+ }
+ }
+ }
+}
+
+QDBusObjectPath KWalletFreedesktopCollection::nextItemPath()
+{
+ return QDBusObjectPath(fdoObjectPath().path() + QChar::fromLatin1('/') + QString::number(m_itemCounter++));
+}
+
+const QString &KWalletFreedesktopCollection::label() const
+{
+ return m_uniqueLabel.label;
+}
+
+void KWalletFreedesktopCollection::setLabel(const QString &newLabel)
+{
+ if (newLabel == label()) {
+ return;
+ }
+
+ const auto oldName = m_uniqueLabel.toName();
+ const auto newUniqLabel = fdoService()->makeUniqueCollectionLabel(newLabel);
+ const auto newName = newUniqLabel.toName();
+
+ int rc = backend()->renameWallet(oldName, newName);
+ if (rc == 0) {
+ const QStringList aliases = fdoService()->readAliasesFor(walletName());
+ m_uniqueLabel = newUniqLabel;
+ const QString newName = walletName();
+ for (const auto &alias : aliases) {
+ fdoService()->updateCollectionAlias(alias, newName);
+ }
+
+ itemAttributes().renameWallet(newName);
+ }
+}
+
+bool KWalletFreedesktopCollection::locked() const
+{
+ return m_handle < 0 || !backend()->isOpen(m_handle);
+}
+
+QList KWalletFreedesktopCollection::items() const
+{
+ QList items;
+
+ for (const auto &item : m_items) {
+ items.push_back(item.second->fdoObjectPath());
+ }
+
+ return items;
+}
+
+qulonglong KWalletFreedesktopCollection::created() const
+{
+ return itemAttributes().birthTime();
+}
+
+qulonglong KWalletFreedesktopCollection::modified() const
+{
+ return itemAttributes().lastModified();
+}
+
+QDBusObjectPath
+KWalletFreedesktopCollection::CreateItem(const PropertiesMap &properties, const FreedesktopSecret &secret, bool replace, QDBusObjectPath &prompt)
+{
+ prompt = QDBusObjectPath("/");
+
+ if (m_handle == -1) {
+ sendErrorReply(QStringLiteral("org.freedesktop.Secret.Error.IsLocked"),
+ QStringLiteral("Collection ") + fdoObjectPath().path() + QStringLiteral(" is locked"));
+ return QDBusObjectPath("/");
+ }
+
+ const auto labelFound = properties.map.find(QStringLiteral("org.freedesktop.Secret.Item.Label"));
+ if (labelFound == properties.map.end()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is missing (org.freedesktop.Secret.Item.Label)"));
+ return QDBusObjectPath("/");
+ }
+ if (!labelFound->canConvert()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is not a string (org.freedesktop.Secret.Item.Label)"));
+ return QDBusObjectPath("/");
+ }
+
+ const QString fdoLabel = labelFound->toString();
+ QString dir, label;
+ QDBusObjectPath itemPath;
+
+ StrStrMap attribs;
+ const auto attribsFound = properties.map.find(QStringLiteral("org.freedesktop.Secret.Item.Attributes"));
+ if (attribsFound != properties.map.end() && attribsFound->canConvert()) {
+ attribs = attribsFound->value();
+ }
+
+ if (replace) {
+ /* Try find item with same attributes */
+ const auto matchedItems = itemAttributes().matchAttributes(attribs);
+
+ if (!matchedItems.empty()) {
+ const auto &entryLoc = matchedItems.constFirst();
+ const auto item = findItemByEntryLocation(entryLoc);
+ if (item) {
+ itemPath = item->fdoObjectPath();
+ dir = entryLoc.folder;
+ label = entryLoc.key;
+ }
+ }
+ }
+
+ if (dir.isEmpty() && label.isEmpty()) {
+ const auto entryLocation = makeUniqueEntryLocation(fdoLabel);
+ dir = entryLocation.folder;
+ label = entryLocation.key;
+ itemPath = nextItemPath();
+ }
+
+ if (label.isEmpty()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is invalid (org.freedesktop.Secret.Item.Label)"));
+ return QDBusObjectPath("/");
+ }
+
+ const qulonglong createTime = QDateTime::currentSecsSinceEpoch();
+ const EntryLocation entryLoc{dir, label};
+ itemAttributes().newItem(entryLoc);
+ itemAttributes().setParam(entryLoc, FDO_KEY_MIME, secret.mimeType);
+ itemAttributes().setParam(entryLoc, FDO_KEY_CREATED, createTime);
+ itemAttributes().setParam(entryLoc, FDO_KEY_MODIFIED, createTime);
+ itemAttributes().setAttributes(entryLoc, attribs);
+
+ pushNewItem(entryLoc.toUniqueLabel(), itemPath);
+
+ {
+ auto decrypted = secret;
+ if (!fdoService()->desecret(message(), decrypted)) {
+ sendErrorReply(QDBusError::ErrorType::InvalidObjectPath, QStringLiteral("Can't find session ") + secret.session.path());
+ return QDBusObjectPath("/");
+ }
+
+ QString xdgSchema = QStringLiteral("org.kde.KWallet.Stream");
+ const auto found = attribs.find(FDO_KEY_XDG_SCHEMA);
+ if (found != attribs.end()) {
+ xdgSchema = found.value();
+ }
+
+ if (xdgSchema == QStringLiteral("org.kde.KWallet.Password") || secret.mimeType.startsWith(QStringLiteral("text/"))) {
+ auto bytes = decrypted.note.toByteArray();
+ auto str = QString::fromUtf8(bytes);
+ backend()->writePassword(walletHandle(), dir, label, str, FDO_APPID);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ explicit_zero_mem(str.data(), str.size() * sizeof(QChar));
+ } else {
+ auto bytes = decrypted.note.toByteArray();
+ backend()->writeEntry(walletHandle(), dir, label, bytes, KWallet::Wallet::Stream, FDO_APPID);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ }
+ }
+
+ onItemCreated(itemPath);
+
+ return itemPath;
+}
+
+QDBusObjectPath KWalletFreedesktopCollection::Delete()
+{
+ const auto name = walletName();
+
+ const QStringList aliases = fdoService()->readAliasesFor(name);
+ for (const QString &alias : aliases) {
+ fdoService()->removeAlias(alias);
+ }
+
+ backend()->deleteWallet(name);
+ QDBusConnection::sessionBus().unregisterObject(fdoObjectPath().path());
+ m_service->onCollectionDeleted(fdoObjectPath());
+
+ return QDBusObjectPath("/");
+}
+
+QList KWalletFreedesktopCollection::SearchItems(const StrStrMap &attributes)
+{
+ QList result;
+
+ for (const auto &entryLoc : m_itemAttribs.matchAttributes(attributes)) {
+ auto *itm = findItemByEntryLocation(entryLoc);
+ if (itm) {
+ result.push_back(itm->fdoObjectPath());
+ }
+ }
+
+ return result;
+}
+
+int KWalletFreedesktopCollection::walletHandle() const
+{
+ return m_handle;
+}
+
+KWalletFreedesktopItem *KWalletFreedesktopCollection::getItemByObjectPath(const QString &objectPath) const
+{
+ const auto found = m_items.find(objectPath);
+ if (found != m_items.end()) {
+ return found->second.get();
+ } else {
+ return nullptr;
+ }
+}
+
+KWalletFreedesktopItem *KWalletFreedesktopCollection::findItemByEntryLocation(const EntryLocation &entryLocation) const
+{
+ const auto uniqLabel = FdoUniqueLabel::fromEntryLocation(entryLocation);
+
+ for (const auto &itemPair : m_items) {
+ auto *item = itemPair.second.get();
+ if (item->uniqueLabel() == uniqLabel) {
+ return item;
+ }
+ }
+
+ return nullptr;
+}
+
+EntryLocation KWalletFreedesktopCollection::makeUniqueEntryLocation(const QString &label)
+{
+ QString dir, name;
+
+ const int slashPos = label.indexOf(QChar::fromLatin1('/'));
+ if (slashPos == -1 || slashPos == label.size() - 1) {
+ dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
+ name = label;
+ } else {
+ dir = label.left(slashPos);
+ name = label.mid(slashPos + 1);
+ }
+
+ int suffix = 0;
+ QString resultName = name;
+ while (backend()->hasEntry(m_handle, dir, resultName, FDO_APPID)) {
+ resultName = FdoUniqueLabel::makeName(name, suffix++);
+ }
+
+ return {dir, resultName};
+}
+
+FdoUniqueLabel KWalletFreedesktopCollection::makeUniqueItemLabel(const QString &label)
+{
+ return makeUniqueEntryLocation(label).toUniqueLabel();
+}
+
+KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(FdoUniqueLabel uniqLabel, const QDBusObjectPath &path)
+{
+ m_items.erase(path.path());
+ auto item = std::make_unique(this, std::move(uniqLabel), path);
+ return *m_items.emplace(path.path(), std::move(item)).first->second;
+}
+
+KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(const QString &label, const QDBusObjectPath &path)
+{
+ return pushNewItem(makeUniqueItemLabel(label), path);
+}
+
+KWalletFreedesktopService *KWalletFreedesktopCollection::fdoService() const
+{
+ return m_service;
+}
+
+KWalletD *KWalletFreedesktopCollection::backend() const
+{
+ return fdoService()->backend();
+}
+
+QDBusObjectPath KWalletFreedesktopCollection::fdoObjectPath() const
+{
+ return m_objectPath;
+}
+
+const FdoUniqueLabel &KWalletFreedesktopCollection::uniqueLabel() const
+{
+ return m_uniqueLabel;
+}
+
+QString KWalletFreedesktopCollection::walletName() const
+{
+ return m_uniqueLabel.toName();
+}
+
+void KWalletFreedesktopCollection::onWalletChangeState(int handle)
+{
+ if (handle == m_handle) {
+ return;
+ }
+
+ if (handle >= 0 && m_handle >= 0) {
+ m_handle = handle;
+ return;
+ }
+
+ m_handle = handle;
+
+ if (m_handle < 0 || !m_items.empty()) {
+ return;
+ }
+
+ const QStringList folderList = backend()->folderList(m_handle, FDO_APPID);
+ for (const QString &folder : folderList) {
+ const QStringList entries = backend()->entryList(m_handle, folder, FDO_APPID);
+
+ for (const auto &entry : entries) {
+ const EntryLocation entryLoc{folder, entry};
+ const auto itm = findItemByEntryLocation(entryLoc);
+ if (!itm) {
+ auto &newItem = pushNewItem(entryLoc.toUniqueLabel(), nextItemPath());
+ Q_EMIT ItemChanged(newItem.fdoObjectPath());
+ } else {
+ Q_EMIT ItemChanged(itm->fdoObjectPath());
+ }
+ }
+ }
+}
+
+void KWalletFreedesktopCollection::onItemCreated(const QDBusObjectPath &item)
+{
+ itemAttributes().updateLastModified();
+ Q_EMIT ItemCreated(item);
+
+ QVariantMap props;
+ props[QStringLiteral("Items")] = QVariant::fromValue(items());
+ onPropertiesChanged(props);
+}
+
+void KWalletFreedesktopCollection::onItemChanged(const QDBusObjectPath &item)
+{
+ itemAttributes().updateLastModified();
+ Q_EMIT ItemChanged(item);
+}
+
+void KWalletFreedesktopCollection::onItemDeleted(const QDBusObjectPath &item)
+{
+ itemAttributes().updateLastModified();
+ const auto itemMapPos = m_items.find(item.path());
+ if (itemMapPos == m_items.end()) {
+ return;
+ }
+ auto *itemPtr = itemMapPos->second.get();
+
+ /* This can be called in the context of the item that is currently being
+ * deleted. Therefore we should schedule deletion on the next event loop iteration
+ */
+ itemPtr->setDeleted();
+ itemPtr->deleteLater();
+ itemMapPos->second.release();
+ m_items.erase(itemMapPos);
+
+ Q_EMIT ItemDeleted(item);
+
+ QVariantMap props;
+ props[QStringLiteral("Items")] = QVariant::fromValue(items());
+ onPropertiesChanged(props);
+}
+
+void KWalletFreedesktopCollection::onPropertiesChanged(const QVariantMap &properties)
+{
+ auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
+ auto args = QVariantList();
+ args << QStringLiteral("org.freedesktop.Secret.Collection") << properties << QStringList();
+ msg.setArguments(args);
+ QDBusConnection::sessionBus().send(msg);
+}
+
+KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes()
+{
+ return m_itemAttribs;
+}
+
+const KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes() const
+{
+ return m_itemAttribs;
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopcollection.h b/src/runtime/kwalletd/kwalletfreedesktopcollection.h
new file mode 100644
index 0000000000000000000000000000000000000000..7f0c6c193c61c0fb695c854e5c8b3d23a630c0f8
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopcollection.h
@@ -0,0 +1,104 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPCOLLECTION_H_
+#define _KWALLETFREEDESKTOPCOLLECTION_H_
+#include "kwalletfreedesktopattributes.h"
+#include "kwalletfreedesktopservice.h"
+
+#define FDO_SECRETS_COLLECTION_PATH FDO_SECRETS_SERVICE_OBJECT "/collection/"
+#define FDO_SECRETS_DEFAULT_DIR "Secret Service"
+#define FDO_KEY_MODIFIED QStringLiteral("$fdo_modified")
+#define FDO_KEY_CREATED QStringLiteral("$fdo_created")
+#define FDO_KEY_MIME QStringLiteral("$fdo_mime_type")
+#define FDO_KEY_XDG_SCHEMA QStringLiteral("xdg:schema")
+
+class KWalletFreedesktopItem;
+
+class KWalletFreedesktopCollection : public QObject, protected QDBusContext
+{
+ /* org.freedesktop.Secret.Collection properties */
+public:
+ Q_PROPERTY(qulonglong Created READ created)
+ qulonglong created() const;
+
+ Q_PROPERTY(qulonglong Modified READ modified)
+ qulonglong modified() const;
+
+ Q_PROPERTY(QList Items READ items)
+ QList items() const;
+
+ Q_PROPERTY(QString Label READ label WRITE setLabel)
+ const QString &label() const;
+ void setLabel(const QString &value);
+
+ Q_PROPERTY(bool Locked READ locked)
+ bool locked() const;
+
+ Q_OBJECT
+
+public:
+ KWalletFreedesktopCollection(KWalletFreedesktopService *service, int handle, const QString &walletName, QDBusObjectPath objectPath);
+
+ KWalletFreedesktopCollection(const KWalletFreedesktopCollection &) = delete;
+ KWalletFreedesktopCollection &operator=(const KWalletFreedesktopCollection &) = delete;
+
+ KWalletFreedesktopCollection(KWalletFreedesktopCollection &&) = delete;
+ KWalletFreedesktopCollection &&operator=(KWalletFreedesktopCollection &&) = delete;
+
+ EntryLocation makeUniqueEntryLocation(const QString &label);
+ FdoUniqueLabel makeUniqueItemLabel(const QString &label);
+
+ QDBusObjectPath nextItemPath();
+
+ KWalletFreedesktopService *fdoService() const;
+ KWalletD *backend() const;
+ QDBusObjectPath fdoObjectPath() const;
+ const FdoUniqueLabel &uniqueLabel() const;
+ QString walletName() const;
+ int walletHandle() const;
+
+ KWalletFreedesktopItem *getItemByObjectPath(const QString &objectPath) const;
+ KWalletFreedesktopItem *findItemByEntryLocation(const EntryLocation &entryLocation) const;
+ KWalletFreedesktopItem &pushNewItem(FdoUniqueLabel label, const QDBusObjectPath &path);
+ KWalletFreedesktopAttributes &itemAttributes();
+ const KWalletFreedesktopAttributes &itemAttributes() const;
+
+ /* Emitters */
+ void onWalletChangeState(int handle);
+ void onItemCreated(const QDBusObjectPath &item);
+ void onItemChanged(const QDBusObjectPath &item);
+ void onItemDeleted(const QDBusObjectPath &item);
+ void onPropertiesChanged(const QVariantMap &properties);
+
+private:
+ KWalletFreedesktopItem &pushNewItem(const QString &label, const QDBusObjectPath &path);
+
+private:
+ KWalletFreedesktopService *m_service;
+ int m_handle;
+ FdoUniqueLabel m_uniqueLabel;
+ QDBusObjectPath m_objectPath;
+ KWalletFreedesktopAttributes m_itemAttribs;
+ std::map> m_items;
+ uint64_t m_itemCounter = 0;
+
+ /* Freedesktop API */
+
+ /* org.freedesktop.Secret.Collection methods */
+public Q_SLOTS:
+ QDBusObjectPath CreateItem(const PropertiesMap &properties, const FreedesktopSecret &secret, bool replace, QDBusObjectPath &prompt);
+ QDBusObjectPath Delete();
+ QList SearchItems(const StrStrMap &attributes);
+
+ /* org.freedesktop.Secret.Service signals */
+Q_SIGNALS:
+ void ItemChanged(const QDBusObjectPath &item);
+ void ItemCreated(const QDBusObjectPath &item);
+ void ItemDeleted(const QDBusObjectPath &item);
+};
+
+#endif
diff --git a/src/runtime/kwalletd/kwalletfreedesktopitem.cpp b/src/runtime/kwalletd/kwalletfreedesktopitem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..16d7a3492ed44521b4663e03ce7500cfec4b1475
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopitem.cpp
@@ -0,0 +1,223 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopitem.h"
+
+#include "kwalletd.h"
+#include "kwalletd_debug.h"
+#include "kwalletfreedesktopcollection.h"
+#include "kwalletfreedesktopitemadaptor.h"
+#include "kwalletfreedesktopservice.h"
+
+KWalletFreedesktopItem::KWalletFreedesktopItem(KWalletFreedesktopCollection *collection, FdoUniqueLabel uniqLabel, QDBusObjectPath path)
+ : m_collection(collection)
+ , m_uniqueLabel(std::move(uniqLabel))
+ , m_path(std::move(path))
+{
+ (void)new KWalletFreedesktopItemAdaptor(this);
+ QDBusConnection::sessionBus().registerObject(fdoObjectPath().path(), this);
+}
+
+KWalletFreedesktopItem::~KWalletFreedesktopItem()
+{
+ onPropertiesChanged(QVariantMap());
+
+ QDBusConnection::sessionBus().unregisterObject(fdoObjectPath().path());
+
+ if (!m_wasDeleted) {
+ m_collection->onItemChanged(fdoObjectPath());
+ }
+}
+
+StrStrMap KWalletFreedesktopItem::attributes() const
+{
+ return fdoCollection()->itemAttributes().getAttributes(m_uniqueLabel);
+}
+
+void KWalletFreedesktopItem::setAttributes(const StrStrMap &value)
+{
+ fdoCollection()->itemAttributes().setAttributes(m_uniqueLabel, value);
+}
+
+qulonglong KWalletFreedesktopItem::created() const
+{
+ return fdoCollection()->itemAttributes().getULongLongParam(m_uniqueLabel, FDO_KEY_CREATED, fdoCollection()->modified());
+}
+
+qulonglong KWalletFreedesktopItem::modified() const
+{
+ return fdoCollection()->itemAttributes().getULongLongParam(m_uniqueLabel, FDO_KEY_MODIFIED, fdoCollection()->modified());
+}
+
+QString KWalletFreedesktopItem::label() const
+{
+ return m_uniqueLabel.label;
+}
+
+void KWalletFreedesktopItem::setLabel(const QString &value)
+{
+ const auto entryLocation = m_uniqueLabel.toEntryLocation();
+ m_uniqueLabel = fdoCollection()->makeUniqueItemLabel(value);
+ const auto newEntryLocation = m_uniqueLabel.toEntryLocation();
+
+ if (newEntryLocation.folder != entryLocation.folder) {
+ const auto data = backend()->readEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ backend()->writeEntry(fdoCollection()->walletHandle(), newEntryLocation.folder, newEntryLocation.key, data, FDO_APPID);
+ backend()->removeEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ } else if (newEntryLocation.key != entryLocation.key) {
+ backend()->renameEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, newEntryLocation.key, FDO_APPID);
+ }
+
+ fdoCollection()->itemAttributes().setParam(entryLocation, FDO_KEY_MODIFIED, static_cast(QDateTime::currentSecsSinceEpoch()));
+ fdoCollection()->itemAttributes().renameLabel(entryLocation, newEntryLocation);
+
+ fdoCollection()->onItemChanged(fdoObjectPath());
+}
+
+bool KWalletFreedesktopItem::locked() const
+{
+ return m_collection->locked();
+}
+
+QString KWalletFreedesktopItem::type() const
+{
+ const auto attribs = fdoCollection()->itemAttributes().getAttributes(m_uniqueLabel);
+ const auto found = attribs.find(FDO_KEY_XDG_SCHEMA);
+ if (found != attribs.end()) {
+ return found.value();
+ } else {
+ return QStringLiteral("org.freedesktop.Secret.Generic");
+ }
+}
+
+void KWalletFreedesktopItem::setType(const QString &value)
+{
+ auto attribs = fdoCollection()->itemAttributes().getAttributes(m_uniqueLabel);
+ attribs[FDO_KEY_XDG_SCHEMA] = value;
+ fdoCollection()->itemAttributes().setAttributes(m_uniqueLabel, attribs);
+}
+
+QDBusObjectPath KWalletFreedesktopItem::Delete()
+{
+ const auto entryLocation = m_uniqueLabel.toEntryLocation();
+
+ backend()->removeEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ QDBusConnection::sessionBus().unregisterObject(fdoObjectPath().path());
+
+ m_collection->onItemDeleted(fdoObjectPath());
+
+ return QDBusObjectPath("/");
+}
+
+FreedesktopSecret KWalletFreedesktopItem::getSecret(const QDBusConnection &connection, const QDBusMessage &message, const QDBusObjectPath &session)
+{
+ const auto entryLocation = m_uniqueLabel.toEntryLocation();
+ const auto mimeType = fdoCollection()->itemAttributes().getStringParam(entryLocation, FDO_KEY_MIME, QStringLiteral("application/octet-stream"));
+
+ FreedesktopSecret fdoSecret;
+
+ const auto entryType = backend()->entryType(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ if (entryType == KWallet::Wallet::Password) {
+ auto password = backend()->readPassword(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ auto bytes = password.toUtf8();
+ fdoSecret = FreedesktopSecret(session, bytes, mimeType);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ explicit_zero_mem(password.data(), password.size() * sizeof(QChar));
+ } else {
+ auto bytes = backend()->readEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, FDO_APPID);
+ fdoSecret = FreedesktopSecret(session, bytes, mimeType);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ }
+
+ if (!fdoService()->ensecret(message, fdoSecret)) {
+ message.setDelayedReply(true);
+ connection.send(message.createErrorReply(QDBusError::ErrorType::UnknownObject, QStringLiteral("Can't find session ") + session.path()));
+ }
+
+ return fdoSecret;
+}
+
+FreedesktopSecret KWalletFreedesktopItem::GetSecret(const QDBusObjectPath &session)
+{
+ return getSecret(connection(), message(), session);
+}
+
+void KWalletFreedesktopItem::SetSecret(const FreedesktopSecret &secret)
+{
+ const auto entryLocation = m_uniqueLabel.toEntryLocation();
+
+ fdoCollection()->itemAttributes().setParam(entryLocation, FDO_KEY_MIME, secret.mimeType);
+ fdoCollection()->itemAttributes().setParam(entryLocation, FDO_KEY_MODIFIED, static_cast(QDateTime::currentSecsSinceEpoch()));
+
+ auto decrypted = secret;
+ if (!fdoService()->desecret(message(), decrypted)) {
+ sendErrorReply(QDBusError::ErrorType::UnknownObject, QStringLiteral("Can't find session ") + secret.session.path());
+ return;
+ }
+
+ QString xdgSchema = QStringLiteral("org.kde.KWallet.Stream");
+ const auto attribs = fdoCollection()->itemAttributes().getAttributes(entryLocation);
+ const auto found = attribs.find(FDO_KEY_XDG_SCHEMA);
+ if (found != attribs.end()) {
+ xdgSchema = found.value();
+ }
+
+ if (xdgSchema == QStringLiteral("org.kde.KWallet.Password") || secret.mimeType.startsWith(QStringLiteral("text/"))) {
+ auto bytes = decrypted.note.toByteArray();
+ auto str = QString::fromUtf8(bytes);
+ backend()->writePassword(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, str, FDO_APPID);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ explicit_zero_mem(str.data(), str.size() * sizeof(QChar));
+ } else {
+ auto bytes = decrypted.note.toByteArray();
+ backend()->writeEntry(fdoCollection()->walletHandle(), entryLocation.folder, entryLocation.key, bytes, KWallet::Wallet::Stream, FDO_APPID);
+ }
+}
+
+KWalletFreedesktopCollection *KWalletFreedesktopItem::fdoCollection() const
+{
+ return m_collection;
+}
+
+KWalletFreedesktopService *KWalletFreedesktopItem::fdoService() const
+{
+ return fdoCollection()->fdoService();
+}
+
+KWalletD *KWalletFreedesktopItem::backend() const
+{
+ return fdoCollection()->fdoService()->backend();
+}
+
+QDBusObjectPath KWalletFreedesktopItem::fdoObjectPath() const
+{
+ return m_path;
+}
+
+const FdoUniqueLabel &KWalletFreedesktopItem::uniqueLabel() const
+{
+ return m_uniqueLabel;
+}
+
+void KWalletFreedesktopItem::uniqueLabel(const FdoUniqueLabel &uniqueLabel)
+{
+ m_uniqueLabel = uniqueLabel;
+}
+
+void KWalletFreedesktopItem::setDeleted()
+{
+ m_wasDeleted = true;
+ fdoCollection()->itemAttributes().remove(m_uniqueLabel);
+}
+
+void KWalletFreedesktopItem::onPropertiesChanged(const QVariantMap &properties)
+{
+ auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
+ auto args = QVariantList();
+ args << QStringLiteral("org.freedesktop.Secret.Item") << properties << QStringList();
+ msg.setArguments(args);
+ QDBusConnection::sessionBus().send(msg);
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopitem.h b/src/runtime/kwalletd/kwalletfreedesktopitem.h
new file mode 100644
index 0000000000000000000000000000000000000000..8fd5b99d716f895f82c1538e6f5180538dec78e1
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopitem.h
@@ -0,0 +1,87 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPITEM_H_
+#define _KWALLETFREEDESKTOPITEM_H_
+#include "kwalletfreedesktopservice.h"
+
+static inline constexpr auto FDO_SS_MAGICK = 0x4950414f44465353ULL;
+
+class KWalletD;
+class KWalletFreedesktopCollection;
+
+class KWalletFreedesktopItem : public QObject, protected FDO_DBUS_CONTEXT
+{
+ /* org.freedesktop.Secret.Item properties */
+public:
+ Q_PROPERTY(StrStrMap Attributes READ attributes WRITE setAttributes)
+ StrStrMap attributes() const;
+ void setAttributes(const StrStrMap &value);
+
+ Q_PROPERTY(qulonglong Created READ created)
+ qulonglong created() const;
+
+ Q_PROPERTY(QString Label READ label WRITE setLabel)
+ QString label() const;
+ void setLabel(const QString &value);
+
+ Q_PROPERTY(bool Locked READ locked)
+ bool locked() const;
+
+ Q_PROPERTY(qulonglong Modified READ modified)
+ qulonglong modified() const;
+
+ Q_PROPERTY(QString Type READ type WRITE setType)
+ QString type() const;
+ void setType(const QString &value);
+
+ Q_OBJECT
+
+public:
+ KWalletFreedesktopItem(KWalletFreedesktopCollection *collection, FdoUniqueLabel uniqLabel, QDBusObjectPath path);
+ ~KWalletFreedesktopItem();
+
+ KWalletFreedesktopItem(const KWalletFreedesktopItem &) = delete;
+ KWalletFreedesktopItem &operator=(const KWalletFreedesktopItem &) = delete;
+
+ KWalletFreedesktopItem(KWalletFreedesktopItem &&) = delete;
+ KWalletFreedesktopItem &operator=(KWalletFreedesktopItem &&) = delete;
+
+ KWalletFreedesktopCollection *fdoCollection() const;
+ KWalletFreedesktopService *fdoService() const;
+ KWalletD *backend() const;
+ QDBusObjectPath fdoObjectPath() const;
+ const FdoUniqueLabel &uniqueLabel() const;
+ void uniqueLabel(const FdoUniqueLabel &uniqLabel);
+
+ /*
+ QVariantMap readMap() const;
+ void writeMap(const QVariantMap &data);
+ */
+
+ FreedesktopSecret getSecret(const QDBusConnection &connection, const QDBusMessage &message, const QDBusObjectPath &session);
+ void setDeleted();
+
+ /* Emitters */
+ void onPropertiesChanged(const QVariantMap &properties);
+ void enableFdoFormat();
+
+private:
+ KWalletFreedesktopCollection *m_collection;
+ FdoUniqueLabel m_uniqueLabel;
+ QDBusObjectPath m_path;
+ bool m_wasDeleted = false;
+
+ /* Freedesktop API */
+
+ /* org.freedesktop.Secret.Item methods */
+public Q_SLOTS:
+ QDBusObjectPath Delete();
+ FreedesktopSecret GetSecret(const QDBusObjectPath &session);
+ void SetSecret(const FreedesktopSecret &secret);
+};
+
+#endif
diff --git a/src/runtime/kwalletd/kwalletfreedesktopprompt.cpp b/src/runtime/kwalletd/kwalletfreedesktopprompt.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..0a5774f4e76f53fc2c834b522401981a6756b932
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopprompt.cpp
@@ -0,0 +1,132 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopprompt.h"
+
+#include "kwalletd.h"
+#include "kwalletfreedesktopcollection.h"
+#include "kwalletfreedesktoppromptadaptor.h"
+#include "kwalletfreedesktopservice.h"
+
+KWalletFreedesktopPrompt::KWalletFreedesktopPrompt(KWalletFreedesktopService *service, QDBusObjectPath objectPath, PromptType type, QString responseBusName)
+ : QObject(nullptr)
+ , m_service(service)
+ , m_objectPath(std::move(objectPath))
+ , m_type(type)
+ , m_responseBusName(std::move(responseBusName))
+{
+ (void)new KWalletFreedesktopPromptAdaptor(this);
+}
+
+KWalletFreedesktopService *KWalletFreedesktopPrompt::fdoService() const
+{
+ return m_service;
+}
+
+KWalletD *KWalletFreedesktopPrompt::backend() const
+{
+ return fdoService()->backend();
+}
+
+QDBusObjectPath KWalletFreedesktopPrompt::fdoObjectPath() const
+{
+ return m_objectPath;
+}
+
+void KWalletFreedesktopPrompt::Dismiss()
+{
+ auto msg = QDBusMessage::createTargetedSignal(m_responseBusName,
+ fdoObjectPath().path(),
+ QStringLiteral("org.freedesktop.Secret.Prompt"),
+ QStringLiteral("Completed"));
+ QVariantList args;
+ args << true << QVariant::fromValue(QDBusVariant(QVariant::fromValue(QList())));
+ msg.setArguments(args);
+ QDBusConnection::sessionBus().send(msg);
+ QDBusConnection::sessionBus().unregisterObject(fdoObjectPath().path());
+}
+
+void KWalletFreedesktopPrompt::Prompt(const QString &window_id)
+{
+ if (m_type != PromptType::Open && m_type != PromptType::Create) {
+ return;
+ }
+
+ const int wId = window_id.toInt();
+ for (auto properties : std::as_const(m_propertiesList)) {
+ /* When type is "PromptType::Open" the properties.label actually stores
+ * the wallet name
+ */
+ QString walletName = properties.collectionLabel;
+
+ if (m_type == PromptType::Create) {
+ walletName = fdoService()->makeUniqueWalletName(properties.collectionLabel);
+ properties.collectionLabel = walletName;
+ }
+
+ if (!properties.alias.isEmpty()) {
+ fdoService()->createCollectionAlias(properties.alias, walletName);
+ }
+
+ const int tId = backend()->openAsync(walletName, wId, FDO_APPID, false, connection(), message());
+ m_transactionIds.insert(tId);
+ m_transactionIdToCollectionProperties.emplace(tId, std::move(properties));
+ }
+}
+
+void KWalletFreedesktopPrompt::walletAsyncOpened(int transactionId, int walletHandle)
+{
+ const auto found = m_transactionIds.find(transactionId);
+
+ if (found != m_transactionIds.end()) {
+ const auto propertiesPos = m_transactionIdToCollectionProperties.find(transactionId);
+ if (walletHandle < 0 || propertiesPos == m_transactionIdToCollectionProperties.end()) {
+ Dismiss();
+ fdoService()->deletePrompt(fdoObjectPath().path());
+ return;
+ }
+ m_transactionIds.remove(transactionId);
+
+ const QString &walletName = propertiesPos->second.collectionLabel;
+ const auto collectionPath = fdoService()->promptUnlockCollection(walletName, walletHandle);
+ m_result.push_back(propertiesPos->second.objectPath.path() == QStringLiteral("/") ? collectionPath : propertiesPos->second.objectPath);
+ }
+
+ if (m_transactionIds.empty()) {
+ /* At this point there is no remaining transactions, so we able to complete prompt */
+
+ auto msg = QDBusMessage::createTargetedSignal(m_responseBusName,
+ fdoObjectPath().path(),
+ QStringLiteral("org.freedesktop.Secret.Prompt"),
+ QStringLiteral("Completed"));
+ QVariantList args;
+ args << false;
+
+ if (m_type == PromptType::Create && m_result.size() < 2) {
+ /* Single object in dbus variant */
+ args << QVariant::fromValue(QDBusVariant(QVariant::fromValue(m_result.empty() ? QDBusObjectPath("/") : m_result.front())));
+ } else {
+ /* Object array in dbus variant */
+ args << QVariant::fromValue(QDBusVariant(QVariant::fromValue(m_result)));
+ }
+
+ msg.setArguments(args);
+ QDBusConnection::sessionBus().send(msg);
+
+ fdoService()->deletePrompt(fdoObjectPath().path());
+ }
+}
+
+void KWalletFreedesktopPrompt::subscribeForWalletAsyncOpened()
+{
+ connect(backend(), &KWalletD::walletAsyncOpened, this, &KWalletFreedesktopPrompt::walletAsyncOpened);
+ QDBusConnection::sessionBus().registerObject(fdoObjectPath().path(), this);
+}
+
+void KWalletFreedesktopPrompt::appendProperties(const QString &label, const QDBusObjectPath &objectPath, const QString &alias)
+{
+ m_propertiesList.push_back(CollectionProperties{label, objectPath, alias});
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopprompt.h b/src/runtime/kwalletd/kwalletfreedesktopprompt.h
new file mode 100644
index 0000000000000000000000000000000000000000..a87ee3230e4a27d88ce16d312d1c6ff5ff2f0ab3
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopprompt.h
@@ -0,0 +1,73 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPPROMPT_H_
+#define _KWALLETFREEDESKTOPPROMPT_H_
+
+#include "kwalletfreedesktopservice.h"
+
+#define FDO_SECRET_SERVICE_PROMPT_PATH FDO_SECRETS_SERVICE_OBJECT "/prompt/"
+
+class KWalletD;
+
+enum class PromptType {
+ Open,
+ Create,
+};
+
+class KWalletFreedesktopPrompt : public QObject, protected FDO_DBUS_CONTEXT
+{
+ Q_OBJECT
+
+ struct CollectionProperties {
+ QString collectionLabel;
+ QDBusObjectPath objectPath;
+ QString alias;
+ };
+
+public:
+ KWalletFreedesktopPrompt(KWalletFreedesktopService *service, QDBusObjectPath objectPath, PromptType type, QString responseBusName);
+
+ KWalletFreedesktopPrompt(const KWalletFreedesktopPrompt &) = delete;
+ KWalletFreedesktopPrompt &operator=(const KWalletFreedesktopPrompt &) = delete;
+
+ KWalletFreedesktopPrompt(KWalletFreedesktopPrompt &&) = delete;
+ KWalletFreedesktopPrompt &operator=(KWalletFreedesktopPrompt &&) = delete;
+
+ KWalletFreedesktopService *fdoService() const;
+ KWalletD *backend() const;
+ QDBusObjectPath fdoObjectPath() const;
+
+ void subscribeForWalletAsyncOpened();
+ void appendProperties(const QString &label, const QDBusObjectPath &objectPath = QDBusObjectPath("/"), const QString &alias = {});
+
+public Q_SLOTS:
+ void walletAsyncOpened(int transactionId, int walletHandle);
+
+private:
+ KWalletFreedesktopService *m_service;
+ QDBusObjectPath m_objectPath;
+ PromptType m_type;
+ QSet m_transactionIds;
+ QList m_result;
+ QList m_propertiesList;
+ std::map m_transactionIdToCollectionProperties;
+ QString m_responseBusName;
+
+ /* Freedesktop API */
+
+ /* org.freedesktop.Secret.Prompt methods */
+public Q_SLOTS:
+ void Dismiss();
+ void Prompt(const QString &window_id);
+
+ /* org.freedesktop.Secret.Prompt signals */
+Q_SIGNALS:
+ /* Emitted manually now */
+ void Completed(bool dismissed, const QDBusVariant &result);
+};
+
+#endif
diff --git a/src/runtime/kwalletd/kwalletfreedesktopservice.cpp b/src/runtime/kwalletd/kwalletfreedesktopservice.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ed6421f93d9e9116d602ac0f760d1b32d00e62d3
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopservice.cpp
@@ -0,0 +1,961 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopservice.h"
+
+#include "kwalletd.h"
+#include "kwalletd_debug.h"
+#include "kwalletfreedesktopcollection.h"
+#include "kwalletfreedesktopitem.h"
+#include "kwalletfreedesktopprompt.h"
+#include "kwalletfreedesktopserviceadaptor.h"
+#include "kwalletfreedesktopsession.h"
+#include
+#include
+#include
+#include
+
+#ifdef Q_OS_WIN
+#include
+#endif
+
+[[maybe_unused]] int DBUS_SECRET_SERVICE_META_TYPE_REGISTER = []() {
+#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
+ qRegisterMetaTypeStreamOperators("StrStrMap");
+ qRegisterMetaTypeStreamOperators>("QMap");
+ qRegisterMetaTypeStreamOperators("QCA::SecureArray");
+#endif
+
+ qDBusRegisterMetaType();
+ qDBusRegisterMetaType>();
+ qDBusRegisterMetaType();
+ qDBusRegisterMetaType();
+ qDBusRegisterMetaType();
+ qDBusRegisterMetaType();
+
+ return 0;
+}();
+
+namespace
+{
+QString mangleInvalidObjectPathChars(const QString &str)
+{
+ const auto utf8Str = str.toUtf8();
+ static constexpr char hex[] = "0123456789abcdef";
+ static_assert(sizeof(hex) == 17);
+
+ QString mangled;
+ mangled.reserve(utf8Str.size());
+
+ for (const auto &c : utf8Str) {
+ if ((c < 'A' || c > 'Z') && (c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_') {
+ const auto cp = static_cast(c);
+ mangled.push_back(QChar::fromLatin1('_'));
+ mangled.push_back(QChar::fromLatin1(hex[cp >> 4]));
+ mangled.push_back(QChar::fromLatin1(hex[cp & 0x0f]));
+ } else {
+ mangled.push_back(QChar::fromLatin1(c));
+ }
+ }
+
+ return mangled;
+}
+}
+
+#define LABEL_NUMBER_PREFIX "__"
+#define LABEL_NUMBER_POSTFIX "_"
+#define LABEL_NUMBER_REGEX "(^.*)" LABEL_NUMBER_PREFIX "(\\d+)" LABEL_NUMBER_POSTFIX "$"
+
+EntryLocation EntryLocation::fromUniqueLabel(const FdoUniqueLabel &uniqLabel)
+{
+ QString dir;
+ QString name = uniqLabel.label;
+
+ const int slashPos = uniqLabel.label.indexOf(QChar::fromLatin1('/'));
+ if (slashPos == -1 || slashPos == uniqLabel.label.size() - 1) {
+ dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
+ } else {
+ dir = uniqLabel.label.left(slashPos);
+ name = uniqLabel.label.right((uniqLabel.label.size() - dir.size()) - 1);
+ }
+
+ return EntryLocation{dir, FdoUniqueLabel::makeName(name, uniqLabel.copyId)};
+}
+
+FdoUniqueLabel EntryLocation::toUniqueLabel() const
+{
+ return FdoUniqueLabel::fromEntryLocation(*this);
+}
+
+FdoUniqueLabel FdoUniqueLabel::fromEntryLocation(const EntryLocation &entryLocation)
+{
+ const auto uniqLabel = FdoUniqueLabel::fromName(entryLocation.key);
+
+ if (entryLocation.folder == QStringLiteral(FDO_SECRETS_DEFAULT_DIR)) {
+ return uniqLabel;
+ } else {
+ return {entryLocation.folder + QChar::fromLatin1('/') + uniqLabel.label, uniqLabel.copyId};
+ }
+}
+
+FdoUniqueLabel FdoUniqueLabel::fromName(const QString &name)
+{
+ static QRegularExpression regexp(QStringLiteral(LABEL_NUMBER_REGEX));
+
+ const auto match = regexp.match(name);
+ if (match.hasMatch()) {
+ const QString strNum = match.captured(2);
+ bool ok = false;
+ const int n = strNum.toInt(&ok);
+ if (ok) {
+ return FdoUniqueLabel{match.captured(1), n};
+ }
+ }
+ return FdoUniqueLabel{name};
+}
+
+QString FdoUniqueLabel::makeName(const QString &label, int n)
+{
+ if (n == -1) {
+ return label;
+ } else {
+ return label + QStringLiteral(LABEL_NUMBER_PREFIX) + QString::number(n) + QStringLiteral(LABEL_NUMBER_POSTFIX);
+ }
+}
+
+QString FdoUniqueLabel::toName() const
+{
+ return makeName(label, copyId);
+}
+
+EntryLocation FdoUniqueLabel::toEntryLocation() const
+{
+ return EntryLocation::fromUniqueLabel(*this);
+}
+
+QString KWalletFreedesktopService::wrapToCollectionPath(const QString &itemPath)
+{
+ /* Take only /org/freedesktop/secrets/collection/collection_name */
+ return itemPath.section(QChar::fromLatin1('/'), 0, 5);
+}
+
+KWalletFreedesktopService::KWalletFreedesktopService(KWalletD *parent)
+ : QObject(nullptr)
+ , m_parent(parent)
+ , m_kwalletrc(QStringLiteral("kwalletrc"))
+{
+ (void)new KWalletFreedesktopServiceAdaptor(this);
+
+ /* register */
+ QDBusConnection::sessionBus().registerService(QStringLiteral("org.freedesktop.secrets"));
+ QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_SECRETS_SERVICE_OBJECT), this);
+
+ const KConfigGroup walletGroup(&m_kwalletrc, "Wallet");
+ if (!parent || !walletGroup.readEntry("Enabled", true)) {
+ return;
+ }
+
+ connect(m_parent, static_cast(&KWalletD::walletClosed), this, &KWalletFreedesktopService::lockCollection);
+ connect(m_parent, &KWalletD::entryUpdated, this, &KWalletFreedesktopService::entryUpdated);
+ connect(m_parent, &KWalletD::entryDeleted, this, &KWalletFreedesktopService::entryDeleted);
+ connect(m_parent, &KWalletD::entryRenamed, this, &KWalletFreedesktopService::entryRenamed);
+ connect(m_parent, &KWalletD::walletDeleted, this, &KWalletFreedesktopService::walletDeleted);
+ connect(m_parent, &KWalletD::walletCreated, this, &KWalletFreedesktopService::walletCreated);
+
+ const auto walletNames = backend()->wallets();
+
+ /* Build collections */
+ for (const QString &walletName : walletNames) {
+ const auto objectPath = makeUniqueObjectPath(walletName);
+ auto collection = std::make_unique(this, -1, walletName, objectPath);
+
+ m_collections.emplace(objectPath.path(), std::move(collection));
+ }
+}
+
+KWalletFreedesktopService::~KWalletFreedesktopService() = default;
+
+QList KWalletFreedesktopService::collections() const
+{
+ QList result;
+ result.reserve(m_collections.size());
+
+ for (const auto &collectionPair : m_collections) {
+ result.push_back(QDBusObjectPath(collectionPair.first));
+ }
+
+ return result;
+}
+
+QDBusObjectPath KWalletFreedesktopService::CreateCollection(const QVariantMap &properties, const QString &alias, QDBusObjectPath &prompt)
+{
+ prompt.setPath(QStringLiteral("/"));
+
+ const auto labelIter = properties.find(QStringLiteral("org.freedesktop.Secret.Collection.Label"));
+ if (labelIter == properties.end()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection.Label property is missing"));
+ return QDBusObjectPath("/");
+ }
+ if (!labelIter->canConvert()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Type of Collection.Label property is invalid"));
+ return QDBusObjectPath("/");
+ }
+
+ prompt = nextPromptPath();
+ auto fdoPromptPtr = std::make_unique(this, prompt, PromptType::Create, message().service());
+ auto &fdoPrompt = *m_prompts.emplace(prompt.path(), std::move(fdoPromptPtr)).first->second;
+
+ fdoPrompt.appendProperties(labelIter->toString(), QDBusObjectPath("/"), alias);
+ fdoPrompt.subscribeForWalletAsyncOpened();
+
+ return QDBusObjectPath("/");
+}
+
+FreedesktopSecretMap KWalletFreedesktopService::GetSecrets(const QList &items, const QDBusObjectPath &session)
+{
+ FreedesktopSecretMap result;
+
+ for (const QDBusObjectPath &itemPath : items) {
+ const auto item = getItemByObjectPath(itemPath);
+
+ if (item) {
+ result.insert(itemPath, item->getSecret(connection(), message(), session));
+ } else {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Can't find item at path ") + itemPath.path());
+ break;
+ }
+ }
+
+ return result;
+}
+
+QList KWalletFreedesktopService::Lock(const QList &objects, QDBusObjectPath &prompt)
+{
+ prompt = QDBusObjectPath("/");
+ QList result;
+
+ /* Try find in active collections */
+ for (const QDBusObjectPath &object : objects) {
+ const QString collectionPath = wrapToCollectionPath(resolveIfAlias(object.path()));
+
+ const auto foundCollection = m_collections.find(collectionPath);
+ if (foundCollection != m_collections.end()) {
+ const int walletHandle = foundCollection->second->walletHandle();
+ const int rc = m_parent->close(walletHandle, true, FDO_APPID, message());
+
+ if (rc == 0) {
+ result.push_back(QDBusObjectPath(collectionPath));
+ } else {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Can't lock object at path ") + collectionPath);
+ }
+ } else {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection at path ") + collectionPath + QStringLiteral(" does not exist"));
+ }
+ }
+
+ return result;
+}
+
+QDBusVariant KWalletFreedesktopService::OpenSession(const QString &algorithm, const QDBusVariant &input, QDBusObjectPath &result)
+{
+ if (algorithm != QStringLiteral("dh-ietf1024-sha256-aes128-cbc-pkcs7")) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs,
+ QStringLiteral("Algorithm ") + algorithm + QStringLiteral(" is not supported. (only dh-ietf1024-sha256-aes128-cbc-pkcs7 is supported)"));
+ return {};
+ }
+
+ if (!input.variant().canConvert()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Second input argument must be a byte array."));
+ return {};
+ }
+
+ const auto sessionPath = createSession(input.variant().toByteArray());
+ result.setPath(sessionPath);
+
+ if (sessionPath != QStringLiteral("/")) {
+ return QDBusVariant(QVariant(m_sessions[sessionPath]->publicKey().toDH().y().toArray().toByteArray()));
+ } else {
+ return QDBusVariant(QVariant(QByteArray()));
+ }
+}
+
+QDBusObjectPath KWalletFreedesktopService::ReadAlias(const QString &name)
+{
+ QString walletName;
+
+ m_kwalletrc.reparseConfiguration();
+ if (name == QStringLiteral("default")) {
+ KConfigGroup cfg(&m_kwalletrc, "Wallet");
+ walletName = defaultWalletName(cfg);
+
+ } else {
+ KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
+ walletName = cfg.readEntry(name, QString());
+ }
+
+ if (!walletName.isEmpty()) {
+ const auto *collection = getCollectionByWalletName(walletName);
+ if (collection) {
+ return collection->fdoObjectPath();
+ }
+ }
+
+ return QDBusObjectPath("/");
+}
+
+QList KWalletFreedesktopService::SearchItems(const StrStrMap &attributes, QList &locked)
+{
+ QList unlocked;
+
+ for (const auto &collectionPair : m_collections) {
+ auto &collection = *collectionPair.second;
+
+ if (collection.locked()) {
+ locked += collection.SearchItems(attributes);
+ } else {
+ unlocked += collection.SearchItems(attributes);
+ }
+ }
+
+ return unlocked;
+}
+
+void KWalletFreedesktopService::SetAlias(const QString &name, const QDBusObjectPath &collectionPath)
+{
+ const auto foundCollection = m_collections.find(collectionPath.path());
+ if (foundCollection == m_collections.end()) {
+ return;
+ }
+
+ auto *collection = foundCollection->second.get();
+ createCollectionAlias(name, collection);
+}
+
+QString KWalletFreedesktopService::resolveIfAlias(QString alias)
+{
+ if (alias.startsWith(QStringLiteral(FDO_ALIAS_PATH))) {
+ const auto path = ReadAlias(alias.remove(0, QStringLiteral(FDO_ALIAS_PATH).size())).path();
+ if (path != QStringLiteral("/")) {
+ alias = path;
+ } else {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Alias ") + alias + QStringLiteral(" does not exist"));
+ return {};
+ }
+ }
+
+ if (!alias.startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Collection object path is invalid"));
+ return {};
+ }
+
+ return alias;
+}
+
+struct UnlockedObject {
+ QString walletName;
+ QDBusObjectPath objectPath;
+};
+
+QList KWalletFreedesktopService::Unlock(const QList &objects, QDBusObjectPath &prompt)
+{
+ prompt = QDBusObjectPath("/");
+
+ QList result;
+ QList needUnlock;
+
+ /* Try find in active collections */
+ for (const QDBusObjectPath &object : objects) {
+ const QString strPath = object.path();
+ const QString collectionPath = wrapToCollectionPath(resolveIfAlias(strPath));
+
+ const auto foundCollection = m_collections.find(collectionPath);
+ if (foundCollection != m_collections.end()) {
+ if (foundCollection->second->locked()) {
+ needUnlock.push_back({foundCollection->second->walletName(), QDBusObjectPath(strPath)});
+ } else {
+ result.push_back(QDBusObjectPath(strPath));
+ }
+ } else {
+ sendErrorReply(QDBusError::ErrorType::InvalidObjectPath, QStringLiteral("Object ") + strPath + QStringLiteral(" does not exist"));
+ return {};
+ }
+ }
+
+ if (!needUnlock.empty()) {
+ const auto promptPath = nextPromptPath();
+ auto fdoPromptPtr = std::make_unique(this, promptPath, PromptType::Open, message().service());
+ auto &fdoPrompt = *m_prompts.emplace(promptPath.path(), std::move(fdoPromptPtr)).first->second;
+
+ prompt = QDBusObjectPath(promptPath);
+
+ for (const auto &[walletName, objectPath] : std::as_const(needUnlock)) {
+ fdoPrompt.appendProperties(walletName, objectPath);
+ }
+
+ fdoPrompt.subscribeForWalletAsyncOpened();
+ }
+ return result;
+}
+
+QString KWalletFreedesktopService::createSession(const QByteArray &clientKey)
+{
+ if (clientKey.size() < FDO_DH_PUBLIC_KEY_SIZE) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Client public key size is invalid"));
+ return QStringLiteral("/");
+ }
+
+ QCA::KeyGenerator keygen;
+ const auto dlGroup = QCA::DLGroup(keygen.createDLGroup(QCA::IETF_1024));
+ if (dlGroup.isNull()) {
+ sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("createDLGroup failed: maybe libqca-ossl is missing"));
+ return QStringLiteral("/");
+ }
+
+ auto privateKey = QCA::PrivateKey(keygen.createDH(dlGroup));
+ const auto publicKey = QCA::PublicKey(privateKey);
+ const auto clientPublicKey = QCA::DHPublicKey(dlGroup, QCA::BigInteger(QCA::SecureArray(clientKey)));
+ const auto commonSecret = privateKey.deriveKey(clientPublicKey);
+ const auto symmetricKey = QCA::HKDF().makeKey(commonSecret, {}, {}, FDO_SECRETS_CIPHER_KEY_SIZE);
+ const QString sessionPath = QStringLiteral(FDO_SECRETS_SESSION_PATH) + QString::number(++m_session_counter);
+
+ auto session = std::make_unique(this, publicKey, symmetricKey, sessionPath, connection(), message());
+ m_sessions[sessionPath] = std::move(session);
+ return sessionPath;
+}
+
+QString KWalletFreedesktopService::defaultWalletName(KConfigGroup &cfg)
+{
+ auto walletName = cfg.readEntry("Default Wallet", "kdewallet");
+ if (walletName.isEmpty()) {
+ walletName = QStringLiteral("kdewallet");
+ }
+ return walletName;
+}
+
+QDBusObjectPath KWalletFreedesktopService::promptUnlockCollection(const QString &walletName, int handle)
+{
+ auto *collection = getCollectionByWalletName(walletName);
+ QString objectPath;
+
+ if (collection) {
+ collection->onWalletChangeState(handle);
+ onCollectionChanged(collection->fdoObjectPath());
+ objectPath = collection->fdoObjectPath().path();
+ } else {
+ const auto path = makeUniqueObjectPath(walletName);
+ objectPath = path.path();
+ auto newCollection = std::make_unique(this, handle, walletName, path);
+ m_collections[objectPath] = std::move(newCollection);
+ onCollectionCreated(path);
+ }
+
+ return QDBusObjectPath(objectPath);
+}
+
+/* Triggered after KWalletD::walletClosed signal */
+void KWalletFreedesktopService::lockCollection(const QString &name)
+{
+ auto *collection = getCollectionByWalletName(name);
+ if (collection) {
+ collection->onWalletChangeState(-1);
+ onCollectionChanged(collection->fdoObjectPath());
+ }
+}
+
+/* Triggered after KWalletD::entryUpdated signal */
+void KWalletFreedesktopService::entryUpdated(const QString &walletName, const QString &folder, const QString &entryName)
+{
+ auto *collection = getCollectionByWalletName(walletName);
+ if (!collection) {
+ return;
+ }
+
+ const EntryLocation entryLocation{folder, entryName};
+ const auto *item = collection->findItemByEntryLocation(entryLocation);
+ if (item) {
+ collection->onItemChanged(item->fdoObjectPath());
+ } else {
+ auto objectPath = collection->nextItemPath();
+ collection->pushNewItem(entryLocation.toUniqueLabel(), objectPath);
+ collection->onItemCreated(objectPath);
+ }
+}
+
+/* Triggered after KWalletD::entryDeleted signal */
+void KWalletFreedesktopService::entryDeleted(const QString &walletName, const QString &folder, const QString &entryName)
+{
+ auto *collection = getCollectionByWalletName(walletName);
+ if (!collection) {
+ return;
+ }
+
+ const auto *item = collection->findItemByEntryLocation({folder, entryName});
+ if (item) {
+ collection->onItemDeleted(item->fdoObjectPath());
+ }
+}
+
+/* Triggered after KWalletD::entryRenamed signal */
+void KWalletFreedesktopService::entryRenamed(const QString &walletName, const QString &folder, const QString &oldName, const QString &newName)
+{
+ auto *collection = getCollectionByWalletName(walletName);
+ if (!collection) {
+ return;
+ }
+
+ const EntryLocation oldLocation{folder, oldName};
+ const EntryLocation newLocation{folder, newName};
+
+ auto *item = collection->findItemByEntryLocation(oldLocation);
+ if (!item) {
+ /* Warn if label not found and not yet renamed */
+ if (!collection->findItemByEntryLocation(newLocation)) {
+ qCWarning(KWALLETD_LOG) << "Cannot rename secret service label:" << FdoUniqueLabel::fromEntryLocation(oldLocation).label;
+ }
+ return;
+ }
+
+ if (item) {
+ collection->itemAttributes().renameLabel(oldLocation, newLocation);
+ item->uniqueLabel(newLocation.toUniqueLabel());
+ collection->onItemChanged(item->fdoObjectPath());
+ }
+}
+
+/* Triggered after KWalletD::walletDeleted signal */
+void KWalletFreedesktopService::walletDeleted(const QString &walletName)
+{
+ auto *collection = getCollectionByWalletName(walletName);
+ if (collection) {
+ collection->Delete();
+ }
+}
+
+/* Triggered after KWalletD::walletCreated signal */
+void KWalletFreedesktopService::walletCreated(const QString &walletName)
+{
+ const auto objectPath = makeUniqueObjectPath(walletName);
+ auto collection = std::make_unique(this, -1, walletName, objectPath);
+ m_collections.emplace(objectPath.path(), std::move(collection));
+ onCollectionCreated(objectPath);
+}
+
+bool KWalletFreedesktopService::desecret(const QDBusMessage &message, FreedesktopSecret &secret)
+{
+ const auto foundSession = m_sessions.find(secret.session.path());
+
+ if (foundSession != m_sessions.end()) {
+ const KWalletFreedesktopSession &session = *foundSession->second;
+ auto decrypted = session.decrypt(message, secret.note, secret.initVector);
+
+ if (decrypted.ok) {
+ secret.note = std::move(decrypted.bytes);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool KWalletFreedesktopService::ensecret(const QDBusMessage &message, FreedesktopSecret &secret)
+{
+ const auto foundSession = m_sessions.find(secret.session.path());
+
+ if (foundSession != m_sessions.end()) {
+ const KWalletFreedesktopSession &session = *foundSession->second;
+ auto encrypted = session.encrypt(message, secret.note, secret.initVector);
+
+ if (encrypted.ok) {
+ secret.note = std::move(encrypted.bytes);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QDBusObjectPath KWalletFreedesktopService::nextPromptPath()
+{
+ static uint64_t id = 0;
+ return QDBusObjectPath(QStringLiteral(FDO_SECRET_SERVICE_PROMPT_PATH) + QStringLiteral("p") + QString::number(id++));
+}
+
+QDBusArgument &operator<<(QDBusArgument &arg, const FreedesktopSecret &secret)
+{
+ arg.beginStructure();
+ arg << secret.session;
+ arg << secret.initVector;
+ arg << secret.note;
+ arg << secret.mimeType;
+ arg.endStructure();
+ return arg;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, FreedesktopSecret &secret)
+{
+ arg.beginStructure();
+ arg >> secret.session;
+ arg >> secret.initVector;
+ arg >> secret.note;
+ arg >> secret.mimeType;
+ arg.endStructure();
+ return arg;
+}
+
+QDataStream &operator<<(QDataStream &stream, const QCA::SecureArray &value)
+{
+ QByteArray bytes = value.toByteArray();
+ stream << bytes;
+ explicit_zero_mem(bytes.data(), bytes.size());
+ return stream;
+}
+
+QDataStream &operator>>(QDataStream &stream, QCA::SecureArray &value)
+{
+ QByteArray bytes;
+ stream >> bytes;
+ value = QCA::SecureArray(bytes);
+ explicit_zero_mem(bytes.data(), bytes.size());
+ return stream;
+}
+
+QDBusArgument &operator<<(QDBusArgument &arg, const QCA::SecureArray &value)
+{
+ QByteArray bytes = value.toByteArray();
+ arg << bytes;
+ explicit_zero_mem(bytes.data(), bytes.size());
+ return arg;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, QCA::SecureArray &buf)
+{
+ QByteArray byteArray;
+ arg >> byteArray;
+ buf = QCA::SecureArray(byteArray);
+ explicit_zero_mem(byteArray.data(), byteArray.size());
+ return arg;
+}
+
+KWalletD *KWalletFreedesktopService::backend() const
+{
+ return m_parent;
+}
+
+QDBusObjectPath KWalletFreedesktopService::fdoObjectPath() const
+{
+ return QDBusObjectPath(FDO_SECRETS_SERVICE_OBJECT);
+}
+
+KWalletFreedesktopItem *KWalletFreedesktopService::getItemByObjectPath(const QDBusObjectPath &path) const
+{
+ const auto str = path.path();
+ if (!str.startsWith(QStringLiteral(FDO_SECRETS_COLLECTION_PATH))) {
+ return nullptr;
+ }
+
+ const QString collectionPath = wrapToCollectionPath(str);
+ const auto collectionPos = m_collections.find(collectionPath);
+ if (collectionPos == m_collections.end()) {
+ return nullptr;
+ }
+
+ const auto &collection = collectionPos->second;
+ return collection->getItemByObjectPath(str);
+}
+
+KWalletFreedesktopPrompt *KWalletFreedesktopService::getPromptByObjectPath(const QDBusObjectPath &path) const
+{
+ const auto foundPrompt = m_prompts.find(path.path());
+ if (foundPrompt != m_prompts.end()) {
+ return foundPrompt->second.get();
+ } else {
+ return nullptr;
+ }
+}
+
+FdoUniqueLabel KWalletFreedesktopService::makeUniqueCollectionLabel(const QString &label)
+{
+ int n = -1;
+ auto walletName = label;
+ const QStringList wallets = backend()->wallets();
+
+ while (wallets.contains(walletName)) {
+ walletName = FdoUniqueLabel::makeName(label, ++n);
+ }
+
+ return {label, n};
+}
+
+QString KWalletFreedesktopService::makeUniqueWalletName(const QString &labelPrefix)
+{
+ return makeUniqueCollectionLabel(labelPrefix).toName();
+}
+
+QDBusObjectPath KWalletFreedesktopService::makeUniqueObjectPath(const QString &walletName) const
+{
+ auto mangled = mangleInvalidObjectPathChars(walletName);
+ mangled.insert(0, QStringLiteral(FDO_SECRETS_COLLECTION_PATH));
+
+ QString result = mangled;
+ int postfix = 0;
+ while (m_collections.count(result)) {
+ result = mangled + QString::number(postfix++);
+ }
+
+ return QDBusObjectPath(result);
+}
+
+QStringList KWalletFreedesktopService::readAliasesFor(const QString &walletName)
+{
+ m_kwalletrc.reparseConfiguration();
+ KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
+ const auto map = cfg.entryMap();
+ QStringList aliases;
+
+ for (auto i = map.begin(); i != map.end(); ++i) {
+ if (i.value() == walletName) {
+ aliases.push_back(i.key());
+ }
+ }
+
+ KConfigGroup cfgWallet(&m_kwalletrc, "Wallet");
+ if (defaultWalletName(cfgWallet) == walletName) {
+ aliases.push_back(QStringLiteral("default"));
+ }
+
+ return aliases;
+}
+
+void KWalletFreedesktopService::updateCollectionAlias(const QString &alias, const QString &walletName)
+{
+ QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
+ QString sectKey = alias;
+
+ if (alias == QStringLiteral("default")) {
+ sectName = QStringLiteral("Wallet");
+ sectKey = QStringLiteral("Default Wallet");
+ }
+
+ KConfigGroup cfg(&m_kwalletrc, sectName);
+ cfg.writeEntry(sectKey, walletName);
+ m_kwalletrc.sync();
+}
+
+void KWalletFreedesktopService::createCollectionAlias(const QString &alias, const QString &walletName)
+{
+ QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
+ QString sectKey = alias;
+
+ if (alias == QStringLiteral("default")) {
+ sectName = QStringLiteral("Wallet");
+ sectKey = QStringLiteral("Default Wallet");
+ }
+
+ m_kwalletrc.reparseConfiguration();
+ KConfigGroup cfg(&m_kwalletrc, sectName);
+
+ const QString prevWalletName = cfg.readEntry(sectKey, QString());
+ if (!prevWalletName.isEmpty()) {
+ const auto *prevCollection = getCollectionByWalletName(prevWalletName);
+ if (prevCollection) {
+ QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
+ }
+ }
+
+ cfg.writeEntry(sectKey, walletName);
+ m_kwalletrc.sync();
+
+ auto *collection = getCollectionByWalletName(walletName);
+ if (collection) {
+ QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, collection);
+ }
+}
+
+void KWalletFreedesktopService::createCollectionAlias(const QString &alias, KWalletFreedesktopCollection *collection)
+{
+ QString sectName = QStringLiteral("org.freedesktop.secrets.aliases");
+ QString sectKey = alias;
+
+ if (alias == QStringLiteral("default")) {
+ sectName = QStringLiteral("Wallet");
+ sectKey = QStringLiteral("Default Wallet");
+ }
+
+ m_kwalletrc.reparseConfiguration();
+ KConfigGroup cfg(&m_kwalletrc, sectName);
+
+ const QString prevWalletName = cfg.readEntry(sectKey, "");
+ if (!prevWalletName.isEmpty()) {
+ const auto *prevCollection = getCollectionByWalletName(prevWalletName);
+ if (prevCollection) {
+ QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
+ }
+ }
+
+ cfg.writeEntry(sectKey, collection->walletName());
+ m_kwalletrc.sync();
+ QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, collection);
+}
+
+void KWalletFreedesktopService::removeAlias(const QString &alias)
+{
+ if (alias == QStringLiteral("default")) {
+ return;
+ }
+
+ KConfigGroup cfg(&m_kwalletrc, "org.freedesktop.secrets.aliases");
+ cfg.deleteEntry(alias);
+ m_kwalletrc.sync();
+ QDBusConnection::sessionBus().unregisterObject(QStringLiteral(FDO_ALIAS_PATH) + alias);
+}
+
+KWalletFreedesktopCollection *KWalletFreedesktopService::getCollectionByWalletName(const QString &walletName) const
+{
+ for (const auto &collectionKeyValue : m_collections) {
+ const auto collection = collectionKeyValue.second.get();
+ if (collection->walletName() == walletName) {
+ return collection;
+ }
+ }
+
+ return nullptr;
+}
+
+void KWalletFreedesktopService::deletePrompt(const QString &objectPath)
+{
+ const auto foundPrompt = m_prompts.find(objectPath);
+ if (foundPrompt == m_prompts.end()) {
+ return;
+ }
+
+ /* This can be called in the context of the prompt that is currently being
+ * deleted. Therefore, we should schedule deletion on the next event loop iteration
+ */
+ foundPrompt->second->deleteLater();
+ foundPrompt->second.release();
+ m_prompts.erase(foundPrompt);
+}
+
+void KWalletFreedesktopService::deleteSession(const QString &objectPath)
+{
+ const auto foundSession = m_sessions.find(objectPath);
+ if (foundSession == m_sessions.end()) {
+ return;
+ }
+
+ /* This can be called in the context of the session that is currently being
+ * deleted. Therefore, we should schedule deletion on the next event loop iteration
+ */
+ foundSession->second->deleteLater();
+ foundSession->second.release();
+ m_sessions.erase(foundSession);
+}
+
+void KWalletFreedesktopService::onCollectionCreated(const QDBusObjectPath &path)
+{
+ Q_EMIT CollectionCreated(path);
+
+ QVariantMap props;
+ props.insert(QStringLiteral("Collections"), QVariant::fromValue(collections()));
+ onPropertiesChanged(props);
+}
+
+void KWalletFreedesktopService::onCollectionChanged(const QDBusObjectPath &path)
+{
+ Q_EMIT CollectionChanged(path);
+}
+
+void KWalletFreedesktopService::onCollectionDeleted(const QDBusObjectPath &path)
+{
+ const auto collectionMapPos = m_collections.find(path.path());
+ if (collectionMapPos == m_collections.end()) {
+ return;
+ }
+ auto &collectionPair = *collectionMapPos;
+ collectionPair.second->itemAttributes().deleteFile();
+
+ /* This can be called in the context of the collection that is currently being
+ * deleted. Therefore, we should schedule deletion on the next event loop iteration
+ */
+ collectionPair.second->deleteLater();
+ collectionPair.second.release();
+ m_collections.erase(collectionMapPos);
+
+ Q_EMIT CollectionDeleted(path);
+
+ QVariantMap props;
+ props[QStringLiteral("Collections")] = QVariant::fromValue(collections());
+ onPropertiesChanged(props);
+}
+
+void KWalletFreedesktopService::onPropertiesChanged(const QVariantMap &properties)
+{
+ auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
+ auto args = QVariantList();
+ args << QStringLiteral("org.freedesktop.Secret.Service") << properties << QStringList();
+ msg.setArguments(args);
+ QDBusConnection::sessionBus().send(msg);
+}
+
+QDataStream &operator<<(QDataStream &stream, const QDBusObjectPath &value)
+{
+ return stream << value.path();
+}
+
+QDataStream &operator>>(QDataStream &stream, QDBusObjectPath &value)
+{
+ QString str;
+ stream >> str;
+ value = QDBusObjectPath(str);
+ return stream;
+}
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, PropertiesMap &value)
+{
+ arg.beginMap();
+ value.map.clear();
+
+ while (!arg.atEnd()) {
+ arg.beginMapEntry();
+ QString key;
+ QVariant val;
+ arg >> key >> val;
+
+ /* For org.freedesktop.Secret.Item.Attributes */
+ if (val.canConvert()) {
+ auto metaArg = val.value();
+ StrStrMap metaMap;
+ metaArg >> metaMap;
+ val = QVariant::fromValue(metaMap);
+ }
+ value.map.insert(key, val);
+
+ arg.endMapEntry();
+ }
+ arg.endMap();
+
+ return arg;
+}
+
+QDBusArgument &operator<<(QDBusArgument &arg, const PropertiesMap &value)
+{
+ arg << value.map;
+ return arg;
+}
+
+void explicit_zero_mem(void *data, size_t size)
+{
+#if defined(KWALLETD_HAVE_EXPLICIT_BZERO)
+ explicit_bzero(data, size);
+#elif defined(KWALLETD_HAVE_RTLSECUREZEROMEMORY)
+ RtlSecureZeroMemory(data, size);
+#else
+ auto p = reinterpret_cast(data);
+ for (size_t i = 0; i < size; ++i) {
+ p[i] = 0;
+ }
+#endif
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopservice.h b/src/runtime/kwalletd/kwalletfreedesktopservice.h
new file mode 100644
index 0000000000000000000000000000000000000000..6b05d56cdc36abd616f6ad5418a45e84d17174fd
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopservice.h
@@ -0,0 +1,228 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPSERVICE_H_
+#define _KWALLETFREEDESKTOPSERVICE_H_
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "kwalletdbuscontext.h"
+
+#define FDO_APPID QString()
+#define FDO_SECRETS_SERVICE_OBJECT "/org/freedesktop/secrets"
+#define FDO_ALIAS_PATH "/org/freedesktop/secrets/aliases/"
+
+static inline constexpr size_t FDO_SECRETS_CIPHER_KEY_SIZE = 16;
+static inline constexpr int FDO_DH_PUBLIC_KEY_SIZE = 128;
+
+class KWalletD;
+
+class FreedesktopSecret
+{
+public:
+ FreedesktopSecret() = default;
+
+ FreedesktopSecret(QDBusObjectPath iSession,
+ const QCA::SecureArray &iNote,
+ QString iMimeType,
+ const QCA::SecureArray &iInitVector = QCA::InitializationVector(FDO_SECRETS_CIPHER_KEY_SIZE))
+ : session(std::move(iSession))
+ , note(iNote)
+ , mimeType(std::move(iMimeType))
+ , initVector(iInitVector)
+ {
+ }
+
+ friend QDBusArgument &operator<<(QDBusArgument &arg, const FreedesktopSecret &secret);
+ friend const QDBusArgument &operator>>(const QDBusArgument &arg, FreedesktopSecret &secret);
+
+ QDBusObjectPath session;
+ QCA::SecureArray note;
+ QString mimeType;
+ QCA::SecureArray initVector;
+};
+
+struct PropertiesMap {
+ QVariantMap map;
+};
+
+struct EntryLocation {
+ static EntryLocation fromUniqueLabel(const struct FdoUniqueLabel &uniqLabel);
+ struct FdoUniqueLabel toUniqueLabel() const;
+
+ bool operator==(const EntryLocation &rhs) const
+ {
+ return folder == rhs.folder && key == rhs.key;
+ }
+
+ bool operator!=(const EntryLocation &rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ QString folder;
+ QString key;
+};
+
+struct FdoUniqueLabel {
+ static FdoUniqueLabel fromEntryLocation(const EntryLocation &entryLocation);
+ static FdoUniqueLabel fromName(const QString &name);
+ static QString makeName(const QString &label, int copyId);
+
+ bool operator==(const FdoUniqueLabel &rhs) const
+ {
+ return copyId == rhs.copyId && label == rhs.label;
+ }
+
+ bool operator!=(const FdoUniqueLabel &rhs) const
+ {
+ return !(*this == rhs);
+ }
+
+ QString toName() const;
+ EntryLocation toEntryLocation() const;
+
+ QString label;
+ int copyId = -1;
+};
+
+typedef QMap FreedesktopSecretMap;
+typedef QMap StrStrMap;
+
+Q_DECLARE_METATYPE(FreedesktopSecret)
+Q_DECLARE_METATYPE(FreedesktopSecretMap)
+Q_DECLARE_METATYPE(PropertiesMap)
+Q_DECLARE_METATYPE(StrStrMap)
+Q_DECLARE_METATYPE(QCA::SecureArray)
+
+class KWalletFreedesktopSession;
+class KWalletFreedesktopCollection;
+class KWalletFreedesktopPrompt;
+class KWalletFreedesktopItem;
+
+class KWalletFreedesktopService : public QObject, protected FDO_DBUS_CONTEXT
+{
+ /* org.freedesktop.Secret.Service properties */
+public:
+ Q_PROPERTY(QList Collections READ collections)
+ QList collections() const;
+
+ Q_OBJECT
+
+public:
+ explicit KWalletFreedesktopService(KWalletD *parent);
+ ~KWalletFreedesktopService();
+
+ KWalletFreedesktopService(const KWalletFreedesktopService &) = delete;
+ KWalletFreedesktopService &operator=(const KWalletFreedesktopService &) = delete;
+
+ KWalletFreedesktopService(KWalletFreedesktopService &&) = delete;
+ KWalletFreedesktopService &operator=(KWalletFreedesktopService &&) = delete;
+
+ static QString wrapToCollectionPath(const QString &itemPath);
+
+ static QDBusObjectPath nextPromptPath();
+ KWalletD *backend() const;
+ QDBusObjectPath fdoObjectPath() const;
+
+ bool desecret(const QDBusMessage &message, FreedesktopSecret &secret);
+ bool ensecret(const QDBusMessage &message, FreedesktopSecret &secret);
+ KWalletFreedesktopItem *getItemByObjectPath(const QDBusObjectPath &path) const;
+ KWalletFreedesktopCollection *getCollectionByWalletName(const QString &walletName) const;
+ KWalletFreedesktopPrompt *getPromptByObjectPath(const QDBusObjectPath &path) const;
+
+ FdoUniqueLabel makeUniqueCollectionLabel(const QString &label);
+ QString makeUniqueWalletName(const QString &labelPrefix);
+ QDBusObjectPath makeUniqueObjectPath(const QString &walletName) const;
+
+ QString resolveIfAlias(QString alias);
+ QStringList readAliasesFor(const QString &walletName);
+ void createCollectionAlias(const QString &alias, KWalletFreedesktopCollection *collection);
+ void createCollectionAlias(const QString &alias, const QString &walletName);
+ void updateCollectionAlias(const QString &alias, const QString &walletName);
+ void removeAlias(const QString &alias);
+
+ void deletePrompt(const QString &objectPath);
+ void deleteSession(const QString &objectPath);
+ QDBusObjectPath promptUnlockCollection(const QString &walletName, int handle);
+
+ /* Emitters */
+ void onCollectionCreated(const QDBusObjectPath &path);
+ void onCollectionChanged(const QDBusObjectPath &path);
+ void onCollectionDeleted(const QDBusObjectPath &path);
+ void onPropertiesChanged(const QVariantMap &properties);
+
+private Q_SLOTS:
+ void lockCollection(const QString &name);
+ void entryUpdated(const QString &walletName, const QString &folder, const QString &entryName);
+ void entryDeleted(const QString &walletName, const QString &folder, const QString &entryName);
+ void entryRenamed(const QString &walletName, const QString &folder, const QString &oldName, const QString &newName);
+ void walletDeleted(const QString &walletName);
+ void walletCreated(const QString &walletCreated);
+ /*
+ void slotServiceOwnerChanged(const QString &name, const QString &oldOwner,
+ const QString &newOwner);
+ */
+
+private:
+ QString createSession(const QByteArray &clientKey);
+ QString defaultWalletName(KConfigGroup &cfg);
+
+private:
+ std::map> m_sessions;
+ std::map> m_collections;
+ std::map> m_prompts;
+
+ uint64_t m_session_counter = 0;
+
+ /*
+ QDBusServiceWatcher _serviceWatcher;
+ */
+ KWalletD *m_parent;
+ QCA::Initializer m_init;
+ KConfig m_kwalletrc;
+
+ /* Freedesktop API */
+
+ /* org.freedesktop.Secret.Service methods */
+public Q_SLOTS:
+ QDBusObjectPath CreateCollection(const QVariantMap &properties, const QString &alias, QDBusObjectPath &prompt);
+ FreedesktopSecretMap GetSecrets(const QList &items, const QDBusObjectPath &session);
+ QList Lock(const QList &objects, QDBusObjectPath &Prompt);
+ QDBusVariant OpenSession(const QString &algorithm, const QDBusVariant &input, QDBusObjectPath &result);
+ QDBusObjectPath ReadAlias(const QString &name);
+ QList SearchItems(const StrStrMap &attributes, QList &locked);
+ void SetAlias(const QString &name, const QDBusObjectPath &collection);
+ QList Unlock(const QList &objects, QDBusObjectPath &prompt);
+
+ /* org.freedesktop.Secret.Service signals */
+Q_SIGNALS:
+ void CollectionChanged(const QDBusObjectPath &collection);
+ void CollectionCreated(const QDBusObjectPath &collection);
+ void CollectionDeleted(const QDBusObjectPath &collection);
+};
+
+QDataStream &operator<<(QDataStream &stream, const QDBusObjectPath &value);
+QDataStream &operator>>(QDataStream &stream, QDBusObjectPath &value);
+
+const QDBusArgument &operator>>(const QDBusArgument &arg, PropertiesMap &value);
+QDBusArgument &operator<<(QDBusArgument &arg, const PropertiesMap &value);
+
+QDataStream &operator<<(QDataStream &stream, const QCA::SecureArray &value);
+QDataStream &operator>>(QDataStream &stream, QCA::SecureArray &value);
+QDBusArgument &operator<<(QDBusArgument &arg, const QCA::SecureArray &value);
+const QDBusArgument &operator>>(const QDBusArgument &arg, QCA::SecureArray &buf);
+
+void explicit_zero_mem(void *data, size_t size);
+
+#endif
diff --git a/src/runtime/kwalletd/kwalletfreedesktopsession.cpp b/src/runtime/kwalletd/kwalletfreedesktopsession.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ad5e971fa215373a97763b97387c23a5218330aa
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopsession.cpp
@@ -0,0 +1,95 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#include "kwalletfreedesktopsession.h"
+
+#include "kwalletfreedesktopservice.h"
+#include "kwalletfreedesktopsessionadaptor.h"
+#include
+
+KWalletFreedesktopSession::KWalletFreedesktopSession(KWalletFreedesktopService *service,
+ const QCA::PublicKey &publicKey,
+ QCA::SymmetricKey symmetricKey,
+ QString sessionPath,
+ const QDBusConnection &connection,
+ const QDBusMessage &message)
+ : m_service(service)
+ , m_publicKey(publicKey)
+ , m_symmetricKey(std::move(symmetricKey))
+ , m_sessionPath(std::move(sessionPath))
+ , m_serviceBusName(message.service())
+{
+ (void)new KWalletFreedesktopSessionAdaptor(this);
+ QDBusConnection::sessionBus().registerObject(m_sessionPath, this);
+
+ m_serviceWatcher.setConnection(connection);
+ m_serviceWatcher.addWatchedService(m_serviceBusName);
+ m_serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
+ connect(&m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &KWalletFreedesktopSession::slotServiceOwnerChanged);
+}
+
+void KWalletFreedesktopSession::slotServiceOwnerChanged(const QString &, const QString &, const QString &)
+{
+ fdoService()->deleteSession(m_sessionPath);
+}
+
+void KWalletFreedesktopSession::Close()
+{
+ if (message().service() != m_serviceBusName) {
+ sendErrorReply(QDBusError::ErrorType::UnknownObject, QStringLiteral("Can't find session ") + m_sessionPath);
+ } else {
+ fdoService()->deleteSession(m_sessionPath);
+ }
+}
+
+CipherResult KWalletFreedesktopSession::encrypt(const QDBusMessage &message, const QCA::SecureArray &bytes, const QCA::SecureArray &initVector) const
+{
+ if (message.service() != m_serviceBusName) {
+ return {false, QByteArray()};
+ }
+
+ auto cipher =
+ QCA::Cipher(QStringLiteral("aes128"), QCA::Cipher::CBC, QCA::Cipher::PKCS7, QCA::Encode, m_symmetricKey, QCA::InitializationVector(initVector));
+ QCA::SecureArray result;
+ result.append(cipher.update(QCA::MemoryRegion(bytes)));
+ if (cipher.ok()) {
+ result.append(cipher.final());
+ }
+
+ return {cipher.ok(), std::move(result)};
+}
+
+CipherResult KWalletFreedesktopSession::decrypt(const QDBusMessage &message, const QCA::SecureArray &bytes, const QCA::SecureArray &initVector) const
+{
+ if (message.service() != m_serviceBusName) {
+ return {false, QByteArray()};
+ }
+
+ auto cipher =
+ QCA::Cipher(QStringLiteral("aes128"), QCA::Cipher::CBC, QCA::Cipher::PKCS7, QCA::Decode, m_symmetricKey, QCA::InitializationVector(initVector));
+ QCA::SecureArray result;
+ result.append(cipher.update(QCA::MemoryRegion(bytes)));
+ if (cipher.ok()) {
+ result.append(cipher.final());
+ }
+
+ return {cipher.ok(), std::move(result)};
+}
+
+KWalletFreedesktopService *KWalletFreedesktopSession::fdoService() const
+{
+ return m_service;
+}
+
+KWalletD *KWalletFreedesktopSession::backend() const
+{
+ return fdoService()->backend();
+}
+
+QDBusObjectPath KWalletFreedesktopSession::fdoObjectPath() const
+{
+ return QDBusObjectPath(m_sessionPath);
+}
diff --git a/src/runtime/kwalletd/kwalletfreedesktopsession.h b/src/runtime/kwalletd/kwalletfreedesktopsession.h
new file mode 100644
index 0000000000000000000000000000000000000000..1860c878517fdfb352432dc519ad44d1b20f809d
--- /dev/null
+++ b/src/runtime/kwalletd/kwalletfreedesktopsession.h
@@ -0,0 +1,72 @@
+/*
+ This file is part of the KDE libraries
+ SPDX-FileCopyrightText: 2021 Slava Aseev
+
+ SPDX-License-Identifier: LGPL-2.0-or-later
+*/
+#ifndef _KWALLETFREEDESKTOPSESSION_H_
+#define _KWALLETFREEDESKTOPSESSION_H_
+
+#include "kwalletfreedesktopservice.h"
+#include
+#include
+#include
+#include
+
+#define FDO_SECRETS_SESSION_PATH FDO_SECRETS_SERVICE_OBJECT "/session/"
+
+class KWalletD;
+
+struct CipherResult {
+ bool ok;
+ QCA::SecureArray bytes;
+};
+
+class KWalletFreedesktopSession : public QObject, protected QDBusContext
+{
+ Q_OBJECT
+
+public:
+ KWalletFreedesktopSession(class KWalletFreedesktopService *parent,
+ const QCA::PublicKey &publicKey,
+ QCA::SymmetricKey symmetricKey,
+ QString sessionPath,
+ const QDBusConnection &connection,
+ const QDBusMessage &message);
+
+ KWalletFreedesktopSession(const KWalletFreedesktopSession &) = delete;
+ KWalletFreedesktopSession &operator=(const KWalletFreedesktopSession &) = delete;
+
+ KWalletFreedesktopSession(KWalletFreedesktopSession &&) = delete;
+ KWalletFreedesktopSession &operator=(KWalletFreedesktopSession &&) = delete;
+
+ KWalletFreedesktopService *fdoService() const;
+ KWalletD *backend() const;
+ QDBusObjectPath fdoObjectPath() const;
+
+ const QCA::PublicKey &publicKey() const
+ {
+ return m_publicKey;
+ }
+ CipherResult encrypt(const QDBusMessage &message, const QCA::SecureArray &bytes, const QCA::SecureArray &initVector) const;
+ CipherResult decrypt(const QDBusMessage &message, const QCA::SecureArray &bytes, const QCA::SecureArray &initVector) const;
+
+private Q_SLOTS:
+ void slotServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner);
+
+private:
+ class KWalletFreedesktopService *m_service;
+ QCA::PublicKey m_publicKey;
+ QCA::SymmetricKey m_symmetricKey;
+ QString m_sessionPath;
+ QString m_serviceBusName;
+ QDBusServiceWatcher m_serviceWatcher;
+
+ /* Freedesktop API */
+
+ /* org.freedesktop.Secret.Session methods */
+public Q_SLOTS:
+ void Close();
+};
+
+#endif
diff --git a/src/runtime/kwalletd/main.cpp b/src/runtime/kwalletd/main.cpp
index 996203475d7db4ab31e952f221bcb22973108f17..541651d1ccd13b2a6a4f4828bd2ff5d144cd42a2 100644
--- a/src/runtime/kwalletd/main.cpp
+++ b/src/runtime/kwalletd/main.cpp
@@ -22,6 +22,7 @@
#include "backend/kwalletbackend.h" //For the hash size
#include "kwalletd.h"
#include "kwalletd_version.h"
+#include "kwalletfreedesktopservice.h"
#ifndef Q_OS_WIN
#include
@@ -194,6 +195,10 @@ int main(int argc, char **argv)
// check if kwallet is disabled
if (!isWalletEnabled()) {
qCDebug(KWALLETD_LOG) << "kwalletd is disabled!";
+
+ /* Do not keep dbus-daemon waiting for the org.freedesktop.secrets if kwallet is disabled */
+ KWalletFreedesktopService(nullptr);
+
return (0);
}