7#include "secretserviceclient.h"
8#include "kwalletd_debug.h"
11#include <KConfigGroup>
12#include <KLocalizedString>
13#include <QDBusConnection>
14#include <QDBusInterface>
16#include <QDBusServiceWatcher>
23const SecretSchema *qtKeychainSchema(
void)
25 static const SecretSchema schema = {
27 SECRET_SCHEMA_DONT_MATCH_NAME,
28 {{
"user", SECRET_SCHEMA_ATTRIBUTE_STRING}, {
"server", SECRET_SCHEMA_ATTRIBUTE_STRING}, {
"type", SECRET_SCHEMA_ATTRIBUTE_STRING}}};
35 void operator()(GList *list)
const
43struct GHashTableDeleter {
44 void operator()(GHashTable *table)
const
47 g_hash_table_destroy(table);
52struct SecretValueDeleter {
53 void operator()(SecretValue *value)
const
56 secret_value_unref(value);
61using GListPtr = std::unique_ptr<GList, GListDeleter>;
62using GHashTablePtr = std::unique_ptr<GHashTable, GHashTableDeleter>;
63using SecretValuePtr = std::unique_ptr<SecretValue, SecretValueDeleter>;
65static bool wasErrorFree(GError **error)
76static QString typeToString(SecretServiceClient::Type type)
80 case SecretServiceClient::Base64:
81 return QStringLiteral(
"base64");
82 case SecretServiceClient::Binary:
83 return QStringLiteral(
"binary");
84 case SecretServiceClient::Map:
85 return QStringLiteral(
"map");
87 return QStringLiteral(
"plaintext");
91static SecretServiceClient::Type stringToType(
const QString &typeName)
93 if (typeName == QStringLiteral(
"binary")) {
94 return SecretServiceClient::Binary;
95 }
else if (typeName == QStringLiteral(
"base64")) {
96 return SecretServiceClient::Base64;
97 }
else if (typeName == QStringLiteral(
"map")) {
98 return SecretServiceClient::Map;
100 return SecretServiceClient::PlainText;
104SecretServiceClient::SecretServiceClient(
QObject *parent)
107 KConfig cfg(QStringLiteral(
"kwalletrc"));
108 KConfigGroup ksecretGroup(&cfg, QStringLiteral(
"KSecretD"));
109 m_useKSecretBackend = ksecretGroup.readEntry(
"Enabled",
true);
111 if (m_useKSecretBackend) {
113 qputenv(
"SECRET_SERVICE_BUS_NAME",
"org.kde.secretservicecompat");
114 m_serviceBusName = QStringLiteral(
"org.kde.secretservicecompat");
116 m_serviceBusName = QStringLiteral(
"org.freedesktop.secrets");
119 m_collectionDirtyTimer =
new QTimer(
this);
120 m_collectionDirtyTimer->setSingleShot(
true);
121 m_collectionDirtyTimer->setInterval(100);
123 for (
const QString &collection : std::as_const(m_dirtyCollections)) {
124 Q_EMIT collectionDirty(collection);
126 m_dirtyCollections.clear();
135 if (attemptConnection()) {
137 for (
const QString &collection : listCollections(&ok)) {
138 watchCollection(collection, &ok);
146 QStringLiteral(
"/org/freedesktop/secrets"),
147 QStringLiteral(
"org.freedesktop.Secret.Service"),
148 QStringLiteral(
"CollectionCreated"),
152 QStringLiteral(
"/org/freedesktop/secrets"),
153 QStringLiteral(
"org.freedesktop.Secret.Service"),
154 QStringLiteral(
"CollectionDeleted"),
161 if (!attemptConnection()) {
166 if (!collectionInterface.isValid()) {
167 qCWarning(KWALLETD_LOG) <<
"Failed to connect to the DBus collection object:" <<
path.path();
171 QVariant reply = collectionInterface.property(
"Label");
174 qCWarning(KWALLETD_LOG) <<
"Error reading label:" << collectionInterface.lastError();
181SecretCollection *SecretServiceClient::retrieveCollection(
const QString &name)
183 if (!attemptConnection()) {
187 auto it = m_openCollections.find(name);
188 if (it != m_openCollections.end()) {
189 return it->second.get();
192 GListPtr collections = GListPtr(secret_service_get_collections(m_service.get()));
194 for (GList *l = collections.get(); l !=
nullptr; l = l->next) {
195 SecretCollectionPtr colPtr = SecretCollectionPtr(SECRET_COLLECTION(l->data));
196 const gchar *
label = secret_collection_get_label(colPtr.get());
198 m_openCollections.insert(std::make_pair(name, std::move(colPtr)));
199 SecretCollection *collection = colPtr.get();
208SecretServiceClient::retrieveItem(
const QString &key,
const SecretServiceClient::Type type,
const QString &folder,
const QString &collectionName,
bool *ok)
210 GError *
error =
nullptr;
212 SecretCollection *collection = retrieveCollection(collectionName);
214 GHashTablePtr attributes = GHashTablePtr(g_hash_table_new(g_str_hash, g_str_equal));
215 g_hash_table_insert(attributes.get(), g_strdup(
"server"), g_strdup(folder.
toUtf8().
constData()));
216 g_hash_table_insert(attributes.get(), g_strdup(
"user"), g_strdup(key.
toUtf8().
constData()));
217 if (type != Unknown) {
218 g_hash_table_insert(attributes.get(), g_strdup(
"type"), g_strdup(typeToString(type).toUtf8().constData()));
221 GListPtr glist = GListPtr(secret_collection_search_sync(collection,
224 static_cast<SecretSearchFlags
>(SECRET_SEARCH_ALL | SECRET_SEARCH_LOAD_SECRETS),
228 *
ok = wasErrorFree(&error);
233 SecretItem *item =
nullptr;
235 GList *iter = glist.get();
236 if (iter !=
nullptr) {
237 item =
static_cast<SecretItem *
>(iter->data);
241 qCWarning(KWALLETD_LOG) <<
i18n(
"Item not found");
245 return SecretItemPtr(item);
248bool SecretServiceClient::attemptConnection()
254 GError *
error =
nullptr;
255 m_service = SecretServicePtr(
256 secret_service_get_sync(
static_cast<SecretServiceFlags
>(SECRET_SERVICE_OPEN_SESSION | SECRET_SERVICE_LOAD_COLLECTIONS),
nullptr, &error));
258 bool ok = wasErrorFree(&error);
260 if (!ok || !m_service) {
261 qCWarning(KWALLETD_LOG) <<
i18n(
"Could not connect to Secret Service");
268void SecretServiceClient::watchCollection(
const QString &collectionName,
bool *ok)
270 if (m_watchedCollections.contains(collectionName)) {
275 SecretCollection *collection = retrieveCollection(collectionName);
277 GObjectPtr<GDBusProxy> proxy = GObjectPtr<GDBusProxy>(G_DBUS_PROXY(collection));
286 QStringLiteral(
"org.freedesktop.Secret.Collection"),
287 QStringLiteral(
"ItemChanged"),
289 SLOT(onSecretItemChanged(QDBusObjectPath)));
292 QStringLiteral(
"org.freedesktop.Secret.Collection"),
293 QStringLiteral(
"ItemCreated"),
295 SLOT(onSecretItemChanged(QDBusObjectPath)));
298 QStringLiteral(
"org.freedesktop.Secret.Collection"),
299 QStringLiteral(
"ItemDeleted"),
301 SLOT(onSecretItemChanged(QDBusObjectPath)));
303 m_watchedCollections.insert(collectionName);
307void SecretServiceClient::onServiceOwnerChanged(
const QString &serviceName,
const QString &oldOwner,
const QString &newOwner)
309 Q_UNUSED(serviceName);
312 bool available = !newOwner.
isEmpty();
314 m_openCollections.clear();
316 if (available && !m_service) {
317 GError *
error =
nullptr;
318 m_service = SecretServicePtr(
319 secret_service_get_sync(
static_cast<SecretServiceFlags
>(SECRET_SERVICE_OPEN_SESSION | SECRET_SERVICE_LOAD_COLLECTIONS),
nullptr, &error));
320 available = wasErrorFree(&error);
327 qDebug() <<
"Secret Service availability changed:" << (available ?
"Available" :
"Unavailable");
331void SecretServiceClient::onCollectionCreated(
const QDBusObjectPath &path)
333 const QString
label = collectionLabelForPath(path);
338 Q_EMIT collectionCreated(label);
339 Q_EMIT collectionListDirty();
342void SecretServiceClient::onCollectionDeleted(
const QDBusObjectPath &path)
345 if (!attemptConnection()) {
351 Q_EMIT collectionListDirty();
354void SecretServiceClient::onSecretItemChanged(
const QDBusObjectPath &path)
356 if (!attemptConnection()) {
361 if (m_updateInProgress) {
362 m_updateInProgress =
false;
369 if (pieces.
length() != 6) {
373 const QString collectionPath = QStringLiteral(
"/") % pieces.
join(QStringLiteral(
"/"));
375 const QString
label = collectionLabelForPath(QDBusObjectPath(collectionPath));
380 m_dirtyCollections.insert(label);
381 m_collectionDirtyTimer->start();
384void SecretServiceClient::handlePrompt(
bool dismissed)
386 Q_EMIT promptClosed(!dismissed);
389bool SecretServiceClient::useKSecretBackend()
const
391 return m_useKSecretBackend;
394bool SecretServiceClient::isAvailable()
const
396 return m_service !=
nullptr;
399bool SecretServiceClient::unlockCollection(
const QString &collectionName,
bool *ok)
401 if (!attemptConnection()) {
406 GError *
error =
nullptr;
408 SecretCollection *collection = retrieveCollection(collectionName);
415 watchCollection(collectionName, ok);
417 gboolean locked = secret_collection_get_locked(collection);
420 gboolean success = secret_service_unlock_sync(m_service.get(), g_list_append(
nullptr, collection),
nullptr,
nullptr, &error);
421 *
ok = wasErrorFree(&error);
423 qCWarning(KWALLETD_LOG) <<
i18n(
"Unable to unlock collectionName %1", collectionName);
431QString SecretServiceClient::defaultCollection(
bool *ok)
433 if (!attemptConnection()) {
438 QString
label = QStringLiteral(
"kdewallet");
440 QDBusInterface serviceInterface(m_serviceBusName,
441 QStringLiteral(
"/org/freedesktop/secrets"),
442 QStringLiteral(
"org.freedesktop.Secret.Service"),
445 if (!serviceInterface.isValid()) {
446 qCWarning(KWALLETD_LOG) <<
"Failed to connect to the DBus SecretService object";
451 QDBusReply<QDBusObjectPath> reply = serviceInterface.call(QStringLiteral(
"ReadAlias"), QStringLiteral(
"default"));
454 qCWarning(KWALLETD_LOG) <<
"Error reading label:" << reply.
error().
message();
459 label = collectionLabelForPath(reply.value());
463 return QStringLiteral(
"kdewallet");
469void SecretServiceClient::setDefaultCollection(
const QString &collectionName,
bool *ok)
471 if (!attemptConnection()) {
476 GError *
error =
nullptr;
478 SecretCollection *collection = retrieveCollection(collectionName);
480 *
ok = secret_service_set_alias_sync(m_service.get(),
"default", collection,
nullptr, &error);
482 *
ok = *
ok && wasErrorFree(&error);
485QStringList SecretServiceClient::listCollections(
bool *ok)
487 if (!attemptConnection()) {
489 return QStringList();
492 QStringList collections;
494 GListPtr glist = GListPtr(secret_service_get_collections(m_service.get()));
497 for (GList *iter = glist.get(); iter !=
nullptr; iter = iter->next) {
498 SecretCollection *collection = SECRET_COLLECTION(iter->data);
499 const gchar *rawLabel = secret_collection_get_label(collection);
502 collections.
append(label);
506 qCDebug(KWALLETD_LOG) <<
i18n(
"No collections");
515 if (!attemptConnection()) {
520 QSet<QString> folders;
522 SecretCollection *collection = retrieveCollection(collectionName);
528 GListPtr glist = GListPtr(secret_collection_get_items(collection));
531 for (GList *iter = glist.get(); iter !=
nullptr; iter = iter->next) {
532 SecretItem *item =
static_cast<SecretItem *
>(iter->data);
534 GHashTable *attributes = secret_item_get_attributes(item);
536 const gchar *value = (
const char *)g_hash_table_lookup(attributes,
"server");
543 qCDebug(KWALLETD_LOG) <<
i18n(
"No entries");
551 if (!attemptConnection()) {
558 GError *
error =
nullptr;
560 SecretCollection *collection = retrieveCollection(collectionName);
567 GHashTablePtr attributes = GHashTablePtr(g_hash_table_new(g_str_hash, g_str_equal));
568 g_hash_table_insert(attributes.get(), g_strdup(
"server"), g_strdup(folder.
toUtf8().
constData()));
570 GListPtr glist = GListPtr(secret_collection_search_sync(collection, qtKeychainSchema(), attributes.get(), SECRET_SEARCH_ALL,
nullptr, &error));
572 *
ok = wasErrorFree(&error);
574 return QStringList();
578 for (GList *iter = glist.get(); iter !=
nullptr; iter = iter->next) {
579 SecretItemPtr item = SecretItemPtr(
static_cast<SecretItem *
>(iter->data));
580 GHashTablePtr attributes = GHashTablePtr(secret_item_get_attributes(item.get()));
583 const gchar *value = (
const char *)g_hash_table_lookup(attributes.get(),
"user");
590 qCDebug(KWALLETD_LOG) <<
i18n(
"No entries");
598 if (!attemptConnection()) {
603 QHash<QString, QString> hash;
605 SecretItemPtr item = retrieveItem(key, Unknown, folder, collectionName, ok);
608 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry not found, key: %1, folder: %2", key, folder);
612 GHashTablePtr attributes = GHashTablePtr(secret_item_get_attributes(item.get()));
615 GHashTableIter attrIter;
617 g_hash_table_iter_init(&attrIter, attributes.get());
618 while (g_hash_table_iter_next(&attrIter, &key, &value)) {
621 hash.
insert(keyString, valueString);
628void SecretServiceClient::createCollection(
const QString &collectionName,
bool *ok)
630 if (!attemptConnection()) {
635 GError *
error =
nullptr;
637 secret_collection_create_sync(m_service.get(), collectionName.
toUtf8().
data(),
nullptr, SECRET_COLLECTION_CREATE_NONE,
nullptr, &error);
639 *
ok = wasErrorFree(&error);
642void SecretServiceClient::deleteCollection(
const QString &collectionName,
bool *ok)
644 if (!attemptConnection()) {
649 GError *
error =
nullptr;
651 SecretCollection *collection = retrieveCollection(collectionName);
653 *
ok = secret_collection_delete_sync(collection,
nullptr, &error);
654 m_openCollections.erase(collectionName);
655 m_watchedCollections.remove(collectionName);
657 *
ok = *
ok && wasErrorFree(&error);
659 Q_EMIT collectionDeleted(collectionName);
663void SecretServiceClient::deleteFolder(
const QString &folder,
const QString &collectionName,
bool *ok)
665 if (!attemptConnection()) {
670 GError *
error =
nullptr;
672 SecretCollection *collection = retrieveCollection(collectionName);
674 GHashTablePtr attributes = GHashTablePtr(g_hash_table_new(g_str_hash, g_str_equal));
675 g_hash_table_insert(attributes.get(), g_strdup(
"server"), g_strdup(folder.
toUtf8().
constData()));
677 GListPtr glist = GListPtr(secret_collection_search_sync(collection, qtKeychainSchema(), attributes.get(), SECRET_SEARCH_ALL,
nullptr, &error));
679 *
ok = wasErrorFree(&error);
685 for (GList *iter = glist.get(); iter !=
nullptr; iter = iter->next) {
686 SecretItem *item =
static_cast<SecretItem *
>(iter->data);
687 m_updateInProgress =
true;
688 secret_item_delete_sync(item,
nullptr, &error);
689 *
ok = wasErrorFree(&error);
690 g_object_unref(item);
693 qCWarning(KWALLETD_LOG) <<
i18n(
"No entries");
698SecretServiceClient::readEntry(
const QString &key,
const SecretServiceClient::Type type,
const QString &folder,
const QString &collectionName,
bool *ok)
700 if (!attemptConnection()) {
705 GError *
error =
nullptr;
708 SecretItemPtr item = retrieveItem(key, type, folder, collectionName, ok);
713 if (secret_item_get_locked(item.get())) {
714 secret_service_unlock_sync(m_service.get(), g_list_append(
nullptr, item.get()),
nullptr,
nullptr, &error);
715 *
ok = wasErrorFree(&error);
717 qCWarning(KWALLETD_LOG) <<
i18n(
"Unable to unlock item");
721 secret_item_load_secret_sync(item.get(),
nullptr, &error);
722 *
ok = wasErrorFree(&error);
725 SecretValuePtr secretValue = SecretValuePtr(secret_item_get_secret(item.get()));
728 if (type == SecretServiceClient::Binary) {
730 const gchar *password = secret_value_get(secretValue.get(), &length);
731 return QByteArray(password, length);
734 const gchar *password = secret_value_get_text(secretValue.get());
735 if (type == SecretServiceClient::Base64) {
738 data = QByteArray(password);
746void SecretServiceClient::renameEntry(
const QString &display_name,
753 SecretItemPtr item = retrieveItem(oldKey, Unknown, folder, collectionName, ok);
760 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry to rename not found");
764 SecretItemPtr existingItem = retrieveItem(newKey, Unknown, folder, collectionName, ok);
767 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry named %1 in folder %2 and wallet %3 already exists.", newKey, folder, collectionName);
773 Type
type = PlainText;
774 GHashTablePtr attributes = GHashTablePtr(secret_item_get_attributes(item.get()));
776 const gchar *value = (
const char *)g_hash_table_lookup(attributes.get(),
"type");
782 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry to rename incomplete");
786 SecretValuePtr secretValue = SecretValuePtr(secret_item_get_secret(item.get()));
788 const gchar *password = secret_value_get_text(secretValue.get());
790 if (type == Binary) {
793 data = QByteArray(password);
797 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry to rename incomplete");
801 deleteEntry(oldKey, folder, collectionName, ok);
805 writeEntry(display_name, newKey, data, type, folder, collectionName, ok);
808void SecretServiceClient::writeEntry(
const QString &display_name,
811 const SecretServiceClient::Type type,
816 if (!attemptConnection()) {
821 GError *
error =
nullptr;
823 SecretCollection *collection = retrieveCollection(collectionName);
826 if (type == SecretServiceClient::Base64) {
833 if (type == SecretServiceClient::Binary) {
834 mimeType = QStringLiteral(
"application/octet-stream");
836 mimeType = QStringLiteral(
"text/plain");
842 qCWarning(KWALLETD_LOG) <<
i18n(
"Failed to create SecretValue");
846 GHashTablePtr attributes = GHashTablePtr(g_hash_table_new(g_str_hash, g_str_equal));
847 g_hash_table_insert(attributes.get(), g_strdup(
"user"), g_strdup(key.
toUtf8().
constData()));
848 g_hash_table_insert(attributes.get(), g_strdup(
"type"), g_strdup(typeToString(type).toUtf8().constData()));
849 g_hash_table_insert(attributes.get(), g_strdup(
"server"), g_strdup(folder.
toUtf8().
constData()));
851 m_updateInProgress =
true;
852 SecretItemPtr item = SecretItemPtr(secret_item_create_sync(collection,
857 SECRET_ITEM_CREATE_REPLACE,
861 *
ok = wasErrorFree(&error);
864void SecretServiceClient::deleteEntry(
const QString &key,
const QString &folder,
const QString &collectionName,
bool *ok)
866 if (!attemptConnection()) {
871 GError *
error =
nullptr;
872 SecretItemPtr item = retrieveItem(key, Unknown, folder, collectionName, ok);
878 qCWarning(KWALLETD_LOG) <<
i18n(
"Entry to rename not found");
881 m_updateInProgress =
true;
882 secret_item_delete_sync(item.get(),
nullptr, &error);
883 *
ok = wasErrorFree(&error);
886#include <moc_secretserviceclient.cpp>
QString i18n(const char *text, const TYPE &arg...)
Type type(const QSqlDatabase &db)
KCALUTILS_EXPORT QString mimeType()
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QString label(StandardShortcut id)
const char * constData() const const
QByteArray fromBase64(const QByteArray &base64, Base64Options options)
QByteArray toBase64(Base64Options options) const const
bool connect(const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *slot)
QDBusConnection sessionBus()
QString message() const const
const QDBusError & error()
bool isValid() const const
void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
iterator insert(const Key &key, const T &value)
void append(QList< T > &&value)
qsizetype length() const const
iterator insert(const T &value)
QList< T > values() const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toLatin1() const const
QByteArray toUtf8() const const
QString join(QChar separator) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isValid() const const
QString toString() const const