KWallet

secretserviceclient.cpp
1/*
2 SPDX-FileCopyrightText: 2024 Marco Martin <notmart@gmail.com>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "secretserviceclient.h"
8#include "kwalletd_debug.h"
9
10#include <KConfig>
11#include <KConfigGroup>
12#include <KLocalizedString>
13#include <QDBusConnection>
14#include <QDBusInterface>
15#include <QDBusReply>
16#include <QDBusServiceWatcher>
17#include <QEventLoop>
18#include <QTimer>
19
20// Copied from
21// https://github.com/frankosterfeld/qtkeychain/blob/main/qtkeychain/libsecret.cpp
22// This is intended to be a data format compatible with QtKeychain
23const SecretSchema *qtKeychainSchema(void)
24{
25 static const SecretSchema schema = {
26 "org.qt.keychain",
27 SECRET_SCHEMA_DONT_MATCH_NAME,
28 {{"user", SECRET_SCHEMA_ATTRIBUTE_STRING}, {"server", SECRET_SCHEMA_ATTRIBUTE_STRING}, {"type", SECRET_SCHEMA_ATTRIBUTE_STRING}}};
29
30 return &schema;
31}
32
33// Custom deleters for non GObject things
34struct GListDeleter {
35 void operator()(GList *list) const
36 {
37 if (list) {
38 g_list_free(list);
39 }
40 }
41};
42
43struct GHashTableDeleter {
44 void operator()(GHashTable *table) const
45 {
46 if (table) {
47 g_hash_table_destroy(table);
48 }
49 }
50};
51
52struct SecretValueDeleter {
53 void operator()(SecretValue *value) const
54 {
55 if (value) {
56 secret_value_unref(value);
57 }
58 }
59};
60
61using GListPtr = std::unique_ptr<GList, GListDeleter>;
62using GHashTablePtr = std::unique_ptr<GHashTable, GHashTableDeleter>;
63using SecretValuePtr = std::unique_ptr<SecretValue, SecretValueDeleter>;
64
65static bool wasErrorFree(GError **error)
66{
67 if (!*error) {
68 return true;
69 }
70 qCWarning(KWALLETD_LOG) << QString::fromUtf8((*error)->message);
71 g_error_free(*error);
72 *error = nullptr;
73 return false;
74}
75
76static QString typeToString(SecretServiceClient::Type type)
77{
78 // Similar to QtKeychain implementation: adds the "map" datatype
79 switch (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");
86 default:
87 return QStringLiteral("plaintext");
88 }
89}
90
91static SecretServiceClient::Type stringToType(const QString &typeName)
92{
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;
99 } else {
100 return SecretServiceClient::PlainText;
101 }
102}
103
104SecretServiceClient::SecretServiceClient(QObject *parent)
105 : QObject(parent)
106{
107 KConfig cfg(QStringLiteral("kwalletrc"));
108 KConfigGroup ksecretGroup(&cfg, QStringLiteral("KSecretD"));
109 m_useKSecretBackend = ksecretGroup.readEntry("Enabled", true);
110
111 if (m_useKSecretBackend) {
112 // Tell libsecret where the secretservice api is
113 qputenv("SECRET_SERVICE_BUS_NAME", "org.kde.secretservicecompat");
114 m_serviceBusName = QStringLiteral("org.kde.secretservicecompat");
115 } else {
116 m_serviceBusName = QStringLiteral("org.freedesktop.secrets");
117 }
118
119 m_collectionDirtyTimer = new QTimer(this);
120 m_collectionDirtyTimer->setSingleShot(true);
121 m_collectionDirtyTimer->setInterval(100);
122 connect(m_collectionDirtyTimer, &QTimer::timeout, this, [this]() {
123 for (const QString &collection : std::as_const(m_dirtyCollections)) {
124 Q_EMIT collectionDirty(collection);
125 }
126 m_dirtyCollections.clear();
127 });
128
129 m_serviceWatcher = new QDBusServiceWatcher(m_serviceBusName, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this);
130
131 connect(m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &SecretServiceClient::onServiceOwnerChanged);
132
133 // Unconditionally try to connect to the service without checking it exists:
134 // it will try to dbus-activate it if not running
135 if (attemptConnection()) {
136 bool ok = false;
137 for (const QString &collection : listCollections(&ok)) {
138 watchCollection(collection, &ok);
139 }
140 }
141
143
144 // React to collections being created, either by us or somebody else
145 bus.connect(m_serviceBusName,
146 QStringLiteral("/org/freedesktop/secrets"),
147 QStringLiteral("org.freedesktop.Secret.Service"),
148 QStringLiteral("CollectionCreated"),
149 this,
150 SLOT(onCollectionCreated(QDBusObjectPath)));
151 bus.connect(m_serviceBusName,
152 QStringLiteral("/org/freedesktop/secrets"),
153 QStringLiteral("org.freedesktop.Secret.Service"),
154 QStringLiteral("CollectionDeleted"),
155 this,
156 SLOT(onCollectionDeleted(QDBusObjectPath)));
157}
158
159QString SecretServiceClient::collectionLabelForPath(const QDBusObjectPath &path)
160{
161 if (!attemptConnection()) {
162 return {};
163 }
164 QDBusInterface collectionInterface(m_serviceBusName, path.path(), QStringLiteral("org.freedesktop.Secret.Collection"), QDBusConnection::sessionBus());
165
166 if (!collectionInterface.isValid()) {
167 qCWarning(KWALLETD_LOG) << "Failed to connect to the DBus collection object:" << path.path();
168 return {};
169 }
170
171 QVariant reply = collectionInterface.property("Label");
172
173 if (!reply.isValid()) {
174 qCWarning(KWALLETD_LOG) << "Error reading label:" << collectionInterface.lastError();
175 return {};
176 }
177
178 return reply.toString();
179}
180
181SecretCollection *SecretServiceClient::retrieveCollection(const QString &name)
182{
183 if (!attemptConnection()) {
184 return nullptr;
185 }
186
187 auto it = m_openCollections.find(name);
188 if (it != m_openCollections.end()) {
189 return it->second.get();
190 }
191
192 GListPtr collections = GListPtr(secret_service_get_collections(m_service.get()));
193
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());
197 if (QString::fromUtf8(label) == name) {
198 m_openCollections.insert(std::make_pair(name, std::move(colPtr)));
199 SecretCollection *collection = colPtr.get();
200 return collection;
201 }
202 }
203
204 return nullptr;
205}
206
207SecretItemPtr
208SecretServiceClient::retrieveItem(const QString &key, const SecretServiceClient::Type type, const QString &folder, const QString &collectionName, bool *ok)
209{
210 GError *error = nullptr;
211
212 SecretCollection *collection = retrieveCollection(collectionName);
213
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()));
219 }
220
221 GListPtr glist = GListPtr(secret_collection_search_sync(collection,
222 qtKeychainSchema(),
223 attributes.get(),
224 static_cast<SecretSearchFlags>(SECRET_SEARCH_ALL | SECRET_SEARCH_LOAD_SECRETS),
225 nullptr,
226 &error));
227
228 *ok = wasErrorFree(&error);
229 if (!*ok) {
230 return nullptr;
231 }
232
233 SecretItem *item = nullptr;
234 if (glist) {
235 GList *iter = glist.get();
236 if (iter != nullptr) {
237 item = static_cast<SecretItem *>(iter->data);
238 }
239
240 } else {
241 qCWarning(KWALLETD_LOG) << i18n("Item not found");
242 *ok = false;
243 }
244
245 return SecretItemPtr(item);
246}
247
248bool SecretServiceClient::attemptConnection()
249{
250 if (m_service) {
251 return true;
252 }
253
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));
257
258 bool ok = wasErrorFree(&error);
259
260 if (!ok || !m_service) {
261 qCWarning(KWALLETD_LOG) << i18n("Could not connect to Secret Service");
262 return false;
263 }
264
265 return true;
266}
267
268void SecretServiceClient::watchCollection(const QString &collectionName, bool *ok)
269{
270 if (m_watchedCollections.contains(collectionName)) {
271 *ok = true;
272 return;
273 }
274
275 SecretCollection *collection = retrieveCollection(collectionName);
276
277 GObjectPtr<GDBusProxy> proxy = GObjectPtr<GDBusProxy>(G_DBUS_PROXY(collection));
278 const QString path = QString::fromUtf8(g_strdup(g_dbus_proxy_get_object_path(proxy.get())));
279 if (path.isEmpty()) {
280 *ok = false;
281 return;
282 }
283
284 QDBusConnection::sessionBus().connect(m_serviceBusName,
285 path,
286 QStringLiteral("org.freedesktop.Secret.Collection"),
287 QStringLiteral("ItemChanged"),
288 this,
289 SLOT(onSecretItemChanged(QDBusObjectPath)));
290 QDBusConnection::sessionBus().connect(m_serviceBusName,
291 path,
292 QStringLiteral("org.freedesktop.Secret.Collection"),
293 QStringLiteral("ItemCreated"),
294 this,
295 SLOT(onSecretItemChanged(QDBusObjectPath)));
296 QDBusConnection::sessionBus().connect(m_serviceBusName,
297 path,
298 QStringLiteral("org.freedesktop.Secret.Collection"),
299 QStringLiteral("ItemDeleted"),
300 this,
301 SLOT(onSecretItemChanged(QDBusObjectPath)));
302
303 m_watchedCollections.insert(collectionName);
304 *ok = true;
305}
306
307void SecretServiceClient::onServiceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner)
308{
309 Q_UNUSED(serviceName);
310 Q_UNUSED(oldOwner);
311
312 bool available = !newOwner.isEmpty();
313
314 m_openCollections.clear();
315
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);
321 }
322
323 if (!available) {
324 m_service.reset();
325 }
326
327 qDebug() << "Secret Service availability changed:" << (available ? "Available" : "Unavailable");
328 Q_EMIT serviceChanged();
329}
330
331void SecretServiceClient::onCollectionCreated(const QDBusObjectPath &path)
332{
333 const QString label = collectionLabelForPath(path);
334 if (label.isEmpty()) {
335 return;
336 }
337
338 Q_EMIT collectionCreated(label);
339 Q_EMIT collectionListDirty();
340}
341
342void SecretServiceClient::onCollectionDeleted(const QDBusObjectPath &path)
343{
344 Q_UNUSED(path);
345 if (!attemptConnection()) {
346 return;
347 }
348
349 // Only emitting collectionListDirty here as we can't know the actual label
350 // of the collection as is already deleted
351 Q_EMIT collectionListDirty();
352}
353
354void SecretServiceClient::onSecretItemChanged(const QDBusObjectPath &path)
355{
356 if (!attemptConnection()) {
357 return;
358 }
359
360 // Don't trigger a reload if we did changes ourselves
361 if (m_updateInProgress) {
362 m_updateInProgress = false;
363 return;
364 }
365
366 QStringList pieces = path.path().split(QStringLiteral("/"), Qt::SkipEmptyParts);
367
368 // 6 items: /org/freedesktop/secrets/collection/collectionName/itemName
369 if (pieces.length() != 6) {
370 return;
371 }
372 pieces.pop_back();
373 const QString collectionPath = QStringLiteral("/") % pieces.join(QStringLiteral("/"));
374
375 const QString label = collectionLabelForPath(QDBusObjectPath(collectionPath));
376 if (label.isEmpty()) {
377 return;
378 }
379
380 m_dirtyCollections.insert(label);
381 m_collectionDirtyTimer->start();
382}
383
384void SecretServiceClient::handlePrompt(bool dismissed)
385{
386 Q_EMIT promptClosed(!dismissed);
387}
388
389bool SecretServiceClient::useKSecretBackend() const
390{
391 return m_useKSecretBackend;
392}
393
394bool SecretServiceClient::isAvailable() const
395{
396 return m_service != nullptr;
397}
398
399bool SecretServiceClient::unlockCollection(const QString &collectionName, bool *ok)
400{
401 if (!attemptConnection()) {
402 *ok = false;
403 return false;
404 }
405
406 GError *error = nullptr;
407
408 SecretCollection *collection = retrieveCollection(collectionName);
409
410 if (!collection) {
411 *ok = false;
412 return false;
413 }
414
415 watchCollection(collectionName, ok);
416
417 gboolean locked = secret_collection_get_locked(collection);
418
419 if (locked) {
420 gboolean success = secret_service_unlock_sync(m_service.get(), g_list_append(nullptr, collection), nullptr, nullptr, &error);
421 *ok = wasErrorFree(&error);
422 if (!success) {
423 qCWarning(KWALLETD_LOG) << i18n("Unable to unlock collectionName %1", collectionName);
424 }
425 return success;
426 }
427
428 return true;
429}
430
431QString SecretServiceClient::defaultCollection(bool *ok)
432{
433 if (!attemptConnection()) {
434 *ok = false;
435 return QString();
436 }
437
438 QString label = QStringLiteral("kdewallet");
439
440 QDBusInterface serviceInterface(m_serviceBusName,
441 QStringLiteral("/org/freedesktop/secrets"),
442 QStringLiteral("org.freedesktop.Secret.Service"),
444
445 if (!serviceInterface.isValid()) {
446 qCWarning(KWALLETD_LOG) << "Failed to connect to the DBus SecretService object";
447 *ok = false;
448 return label;
449 }
450
451 QDBusReply<QDBusObjectPath> reply = serviceInterface.call(QStringLiteral("ReadAlias"), QStringLiteral("default"));
452
453 if (!reply.isValid()) {
454 qCWarning(KWALLETD_LOG) << "Error reading label:" << reply.error().message();
455 *ok = false;
456 return label;
457 }
458
459 label = collectionLabelForPath(reply.value());
460
461 if (label.isEmpty()) {
462 *ok = false;
463 return QStringLiteral("kdewallet");
464 }
465
466 return label;
467}
468
469void SecretServiceClient::setDefaultCollection(const QString &collectionName, bool *ok)
470{
471 if (!attemptConnection()) {
472 *ok = false;
473 return;
474 }
475
476 GError *error = nullptr;
477
478 SecretCollection *collection = retrieveCollection(collectionName);
479
480 *ok = secret_service_set_alias_sync(m_service.get(), "default", collection, nullptr, &error);
481
482 *ok = *ok && wasErrorFree(&error);
483}
484
485QStringList SecretServiceClient::listCollections(bool *ok)
486{
487 if (!attemptConnection()) {
488 *ok = false;
489 return QStringList();
490 }
491
492 QStringList collections;
493
494 GListPtr glist = GListPtr(secret_service_get_collections(m_service.get()));
495
496 if (glist) {
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);
500 const QString label = QString::fromUtf8(rawLabel);
501 if (!label.isEmpty()) {
502 collections.append(label);
503 }
504 }
505 } else {
506 qCDebug(KWALLETD_LOG) << i18n("No collections");
507 }
508
509 *ok = true;
510 return collections;
511}
512
513QStringList SecretServiceClient::listFolders(const QString &collectionName, bool *ok)
514{
515 if (!attemptConnection()) {
516 *ok = false;
517 return {};
518 }
519
520 QSet<QString> folders;
521
522 SecretCollection *collection = retrieveCollection(collectionName);
523
524 if (!collection) {
525 *ok = false;
526 return {};
527 }
528 GListPtr glist = GListPtr(secret_collection_get_items(collection));
529
530 if (glist) {
531 for (GList *iter = glist.get(); iter != nullptr; iter = iter->next) {
532 SecretItem *item = static_cast<SecretItem *>(iter->data);
533
534 GHashTable *attributes = secret_item_get_attributes(item);
535 if (attributes) {
536 const gchar *value = (const char *)g_hash_table_lookup(attributes, "server");
537 if (value) {
538 folders.insert(QString::fromUtf8(value));
539 }
540 }
541 }
542 } else {
543 qCDebug(KWALLETD_LOG) << i18n("No entries");
544 }
545 *ok = true;
546 return folders.values();
547}
548
549QStringList SecretServiceClient::listEntries(const QString &folder, const QString &collectionName, bool *ok)
550{
551 if (!attemptConnection()) {
552 *ok = false;
553 return {};
554 }
555
556 // TODO: deduplicate
557 QSet<QString> items;
558 GError *error = nullptr;
559
560 SecretCollection *collection = retrieveCollection(collectionName);
561
562 if (!collection) {
563 *ok = false;
564 return {};
565 }
566
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()));
569
570 GListPtr glist = GListPtr(secret_collection_search_sync(collection, qtKeychainSchema(), attributes.get(), SECRET_SEARCH_ALL, nullptr, &error));
571
572 *ok = wasErrorFree(&error);
573 if (!*ok) {
574 return QStringList();
575 }
576
577 if (glist) {
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()));
581
582 if (attributes) {
583 const gchar *value = (const char *)g_hash_table_lookup(attributes.get(), "user");
584 if (value) {
585 items.insert(QString::fromUtf8(value));
586 }
587 }
588 }
589 } else {
590 qCDebug(KWALLETD_LOG) << i18n("No entries");
591 }
592
593 return items.values();
594}
595
596QHash<QString, QString> SecretServiceClient::readMetadata(const QString &key, const QString &folder, const QString &collectionName, bool *ok)
597{
598 if (!attemptConnection()) {
599 *ok = false;
600 return {};
601 }
602
603 QHash<QString, QString> hash;
604
605 SecretItemPtr item = retrieveItem(key, Unknown, folder, collectionName, ok);
606
607 if (!item) {
608 qCWarning(KWALLETD_LOG) << i18n("Entry not found, key: %1, folder: %2", key, folder);
609 return hash;
610 }
611
612 GHashTablePtr attributes = GHashTablePtr(secret_item_get_attributes(item.get()));
613
614 if (attributes) {
615 GHashTableIter attrIter;
616 gpointer key, value;
617 g_hash_table_iter_init(&attrIter, attributes.get());
618 while (g_hash_table_iter_next(&attrIter, &key, &value)) {
619 QString keyString = QString::fromUtf8(static_cast<gchar *>(key));
620 QString valueString = QString::fromUtf8(static_cast<gchar *>(value));
621 hash.insert(keyString, valueString);
622 }
623 }
624
625 return hash;
626}
627
628void SecretServiceClient::createCollection(const QString &collectionName, bool *ok)
629{
630 if (!attemptConnection()) {
631 *ok = false;
632 return;
633 }
634
635 GError *error = nullptr;
636
637 secret_collection_create_sync(m_service.get(), collectionName.toUtf8().data(), nullptr, SECRET_COLLECTION_CREATE_NONE, nullptr, &error);
638
639 *ok = wasErrorFree(&error);
640}
641
642void SecretServiceClient::deleteCollection(const QString &collectionName, bool *ok)
643{
644 if (!attemptConnection()) {
645 *ok = false;
646 return;
647 }
648
649 GError *error = nullptr;
650
651 SecretCollection *collection = retrieveCollection(collectionName);
652
653 *ok = secret_collection_delete_sync(collection, nullptr, &error);
654 m_openCollections.erase(collectionName);
655 m_watchedCollections.remove(collectionName);
656
657 *ok = *ok && wasErrorFree(&error);
658 if (ok) {
659 Q_EMIT collectionDeleted(collectionName);
660 }
661}
662
663void SecretServiceClient::deleteFolder(const QString &folder, const QString &collectionName, bool *ok)
664{
665 if (!attemptConnection()) {
666 *ok = false;
667 return;
668 }
669
670 GError *error = nullptr;
671
672 SecretCollection *collection = retrieveCollection(collectionName);
673
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()));
676
677 GListPtr glist = GListPtr(secret_collection_search_sync(collection, qtKeychainSchema(), attributes.get(), SECRET_SEARCH_ALL, nullptr, &error));
678
679 *ok = wasErrorFree(&error);
680 if (!*ok) {
681 return;
682 }
683
684 if (glist) {
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);
691 }
692 } else {
693 qCWarning(KWALLETD_LOG) << i18n("No entries");
694 }
695}
696
698SecretServiceClient::readEntry(const QString &key, const SecretServiceClient::Type type, const QString &folder, const QString &collectionName, bool *ok)
699{
700 if (!attemptConnection()) {
701 *ok = false;
702 return {};
703 }
704
705 GError *error = nullptr;
706 QByteArray data;
707
708 SecretItemPtr item = retrieveItem(key, type, folder, collectionName, ok);
709
710 if (item) {
711 // Some providers like KeepassXC lock each item individually, and need to be
712 // unlocked by the user prior being able to access
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);
716 if (!ok) {
717 qCWarning(KWALLETD_LOG) << i18n("Unable to unlock item");
718 return data;
719 }
720
721 secret_item_load_secret_sync(item.get(), nullptr, &error);
722 *ok = wasErrorFree(&error);
723 }
724
725 SecretValuePtr secretValue = SecretValuePtr(secret_item_get_secret(item.get()));
726
727 if (secretValue) {
728 if (type == SecretServiceClient::Binary) {
729 gsize length = 0;
730 const gchar *password = secret_value_get(secretValue.get(), &length);
731 return QByteArray(password, length);
732 }
733
734 const gchar *password = secret_value_get_text(secretValue.get());
735 if (type == SecretServiceClient::Base64) {
736 data = QByteArray::fromBase64(QByteArray(password));
737 } else {
738 data = QByteArray(password);
739 }
740 }
741 }
742
743 return data;
744}
745
746void SecretServiceClient::renameEntry(const QString &display_name,
747 const QString &oldKey,
748 const QString &newKey,
749 const QString &folder,
750 const QString &collectionName,
751 bool *ok)
752{
753 SecretItemPtr item = retrieveItem(oldKey, Unknown, folder, collectionName, ok);
754
755 if (!*ok) {
756 return;
757 }
758 if (!item) {
759 *ok = false;
760 qCWarning(KWALLETD_LOG) << i18n("Entry to rename not found");
761 return;
762 }
763
764 SecretItemPtr existingItem = retrieveItem(newKey, Unknown, folder, collectionName, ok);
765 if (existingItem) {
766 *ok = false;
767 qCWarning(KWALLETD_LOG) << i18n("Entry named %1 in folder %2 and wallet %3 already exists.", newKey, folder, collectionName);
768 return;
769 }
770
771 QByteArray data;
772
773 Type type = PlainText;
774 GHashTablePtr attributes = GHashTablePtr(secret_item_get_attributes(item.get()));
775 if (attributes) {
776 const gchar *value = (const char *)g_hash_table_lookup(attributes.get(), "type");
777 if (value) {
778 type = stringToType(QString::fromUtf8(value));
779 }
780 } else {
781 *ok = false;
782 qCWarning(KWALLETD_LOG) << i18n("Entry to rename incomplete");
783 return;
784 }
785
786 SecretValuePtr secretValue = SecretValuePtr(secret_item_get_secret(item.get()));
787 if (secretValue) {
788 const gchar *password = secret_value_get_text(secretValue.get());
789
790 if (type == Binary) {
791 data = QByteArray::fromBase64(QByteArray(password));
792 } else {
793 data = QByteArray(password);
794 }
795 } else {
796 *ok = false;
797 qCWarning(KWALLETD_LOG) << i18n("Entry to rename incomplete");
798 return;
799 }
800
801 deleteEntry(oldKey, folder, collectionName, ok);
802 if (!*ok) {
803 return;
804 }
805 writeEntry(display_name, newKey, data, type, folder, collectionName, ok);
806}
807
808void SecretServiceClient::writeEntry(const QString &display_name,
809 const QString &key,
810 const QByteArray &value,
811 const SecretServiceClient::Type type,
812 const QString &folder,
813 const QString &collectionName,
814 bool *ok)
815{
816 if (!attemptConnection()) {
817 *ok = false;
818 return;
819 }
820
821 GError *error = nullptr;
822
823 SecretCollection *collection = retrieveCollection(collectionName);
824
825 QByteArray data;
826 if (type == SecretServiceClient::Base64) {
827 data = value.toBase64();
828 } else {
829 data = value;
830 }
831
832 QString mimeType;
833 if (type == SecretServiceClient::Binary) {
834 mimeType = QStringLiteral("application/octet-stream");
835 } else {
836 mimeType = QStringLiteral("text/plain");
837 }
838
839 SecretValuePtr secretValue = SecretValuePtr(secret_value_new(data.constData(), -1, mimeType.toLatin1().constData()));
840 if (!secretValue) {
841 *ok = false;
842 qCWarning(KWALLETD_LOG) << i18n("Failed to create SecretValue");
843 return;
844 }
845
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()));
850
851 m_updateInProgress = true;
852 SecretItemPtr item = SecretItemPtr(secret_item_create_sync(collection,
853 qtKeychainSchema(),
854 attributes.get(),
855 display_name.toUtf8().constData(),
856 secretValue.get(),
857 SECRET_ITEM_CREATE_REPLACE,
858 nullptr,
859 &error));
860
861 *ok = wasErrorFree(&error);
862}
863
864void SecretServiceClient::deleteEntry(const QString &key, const QString &folder, const QString &collectionName, bool *ok)
865{
866 if (!attemptConnection()) {
867 *ok = false;
868 return;
869 }
870
871 GError *error = nullptr;
872 SecretItemPtr item = retrieveItem(key, Unknown, folder, collectionName, ok);
873 if (!*ok) {
874 return;
875 }
876 if (!item) {
877 *ok = false;
878 qCWarning(KWALLETD_LOG) << i18n("Entry to rename not found");
879 return;
880 }
881 m_updateInProgress = true;
882 secret_item_delete_sync(item.get(), nullptr, &error);
883 *ok = wasErrorFree(&error);
884}
885
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
char * data()
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
void pop_back()
Q_EMITQ_EMIT
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
SkipEmptyParts
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void timeout()
bool isValid() const const
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:53:00 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.