KWallet

kwalletfreedesktopcollection.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2021 Slava Aseev <nullptrnine@basealt.ru>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7#include "kwalletfreedesktopcollection.h"
8
9#include "ksecretd.h"
10#include "kwalletfreedesktopcollectionadaptor.h"
11#include "kwalletfreedesktopitem.h"
12
13KWalletFreedesktopCollection::KWalletFreedesktopCollection(KWalletFreedesktopService *service,
14 int handle,
15 const QString &walletName,
16 QDBusObjectPath objectPath)
17 : m_service(service)
18 , m_handle(handle)
19 , m_uniqueLabel(FdoUniqueLabel::fromName(walletName))
20 , m_objectPath(std::move(objectPath))
21 , m_itemAttribs(walletName)
22{
23 (void)new KWalletFreedesktopCollectionAdaptor(this);
24 QDBusConnection::sessionBus().registerObject(fdoObjectPath().path(), this);
25
26 const QStringList aliases = fdoService()->readAliasesFor(walletName);
27 for (const auto &alias : aliases) {
28 QDBusConnection::sessionBus().registerObject(QStringLiteral(FDO_ALIAS_PATH) + alias, this);
29 }
30
31 onWalletChangeState(handle);
32
33 /* Create items described in the attributes file */
34 if (m_handle == -1) {
35 const auto items = itemAttributes().listItems();
36 for (const auto &entryLocation : items) {
37 if (!findItemByEntryLocation(entryLocation)) {
38 pushNewItem(entryLocation.toUniqueLabel(), nextItemPath());
39 }
40 }
41 }
42}
43
44QDBusObjectPath KWalletFreedesktopCollection::nextItemPath()
45{
46 return QDBusObjectPath(fdoObjectPath().path() + QChar::fromLatin1('/') + QString::number(m_itemCounter++));
47}
48
49const QString &KWalletFreedesktopCollection::label() const
50{
51 return m_uniqueLabel.label;
52}
53
54void KWalletFreedesktopCollection::setLabel(const QString &newLabel)
55{
56 if (newLabel == label()) {
57 return;
58 }
59
60 const auto oldName = m_uniqueLabel.toName();
61 const auto newUniqLabel = fdoService()->makeUniqueCollectionLabel(newLabel);
62 const auto newName = newUniqLabel.toName();
63
64 int rc = backend()->renameWallet(oldName, newName);
65 if (rc == 0) {
66 const QStringList aliases = fdoService()->readAliasesFor(walletName());
67 m_uniqueLabel = newUniqLabel;
68 const QString newName = walletName();
69 for (const auto &alias : aliases) {
70 fdoService()->updateCollectionAlias(alias, newName);
71 }
72
73 itemAttributes().renameWallet(newName);
74 }
75}
76
77bool KWalletFreedesktopCollection::locked() const
78{
79 return m_handle < 0 || !backend()->isOpen(m_handle);
80}
81
82QList<QDBusObjectPath> KWalletFreedesktopCollection::items() const
83{
84 QList<QDBusObjectPath> items;
85
86 for (const auto &item : m_items) {
87 items.push_back(item.second->fdoObjectPath());
88 }
89
90 return items;
91}
92
93qulonglong KWalletFreedesktopCollection::created() const
94{
95 return itemAttributes().birthTime();
96}
97
98qulonglong KWalletFreedesktopCollection::modified() const
99{
100 return itemAttributes().lastModified();
101}
102
104KWalletFreedesktopCollection::CreateItem(const PropertiesMap &properties, const FreedesktopSecret &secret, bool replace, QDBusObjectPath &prompt)
105{
106 prompt = QDBusObjectPath("/");
107
108 if (m_handle == -1) {
109 sendErrorReply(QStringLiteral("org.freedesktop.Secret.Error.IsLocked"),
110 QStringLiteral("Collection ") + fdoObjectPath().path() + QStringLiteral(" is locked"));
111 return QDBusObjectPath("/");
112 }
113
114 const auto labelFound = properties.map.find(QStringLiteral("org.freedesktop.Secret.Item.Label"));
115 if (labelFound == properties.map.end()) {
116 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is missing (org.freedesktop.Secret.Item.Label)"));
117 return QDBusObjectPath("/");
118 }
119 if (!labelFound->canConvert<QString>()) {
120 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is not a string (org.freedesktop.Secret.Item.Label)"));
121 return QDBusObjectPath("/");
122 }
123
124 const QString fdoLabel = labelFound->toString();
125 QString dir, label;
126 QDBusObjectPath itemPath;
127
128 StrStrMap attribs;
129 const auto attribsFound = properties.map.find(QStringLiteral("org.freedesktop.Secret.Item.Attributes"));
130 if (attribsFound != properties.map.end() && attribsFound->canConvert<StrStrMap>()) {
131 attribs = attribsFound->value<StrStrMap>();
132 }
133
134 if (replace) {
135 /* Try find item with same attributes */
136 const auto matchedItems = itemAttributes().matchAttributes(attribs);
137
138 if (!matchedItems.empty()) {
139 const auto &entryLoc = matchedItems.constFirst();
140 const auto item = findItemByEntryLocation(entryLoc);
141 if (item) {
142 itemPath = item->fdoObjectPath();
143 dir = entryLoc.folder;
144 label = entryLoc.key;
145 }
146 }
147 }
148
149 if (dir.isEmpty() && label.isEmpty()) {
150 const auto entryLocation = makeUniqueEntryLocation(fdoLabel);
151 dir = entryLocation.folder;
152 label = entryLocation.key;
153 itemPath = nextItemPath();
154 }
155
156 if (label.isEmpty()) {
157 sendErrorReply(QDBusError::ErrorType::InvalidArgs, QStringLiteral("Item label is invalid (org.freedesktop.Secret.Item.Label)"));
158 return QDBusObjectPath("/");
159 }
160
161 const qulonglong createTime = QDateTime::currentSecsSinceEpoch();
162 const EntryLocation entryLoc{dir, label};
163 itemAttributes().newItem(entryLoc);
164 itemAttributes().setParam(entryLoc, FDO_KEY_MIME, secret.mimeType);
165 itemAttributes().setParam(entryLoc, FDO_KEY_CREATED, createTime);
166 itemAttributes().setParam(entryLoc, FDO_KEY_MODIFIED, createTime);
167 itemAttributes().setAttributes(entryLoc, attribs);
168
169 pushNewItem(entryLoc.toUniqueLabel(), itemPath);
170
171 {
172 auto decrypted = secret;
173 if (!fdoService()->desecret(message(), decrypted)) {
174 sendErrorReply(QDBusError::ErrorType::InvalidObjectPath, QStringLiteral("Can't find session ") + secret.session.path());
175 return QDBusObjectPath("/");
176 }
177
178 QString xdgSchema = QStringLiteral("org.kde.KWallet.Stream");
179 const auto found = attribs.find(FDO_KEY_XDG_SCHEMA);
180 if (found != attribs.end()) {
181 xdgSchema = found.value();
182 }
183
184 const QString typeString = attribs.value(QStringLiteral("type"));
185
186 if (typeString == QStringLiteral("map")) {
187 QJsonObject obj = QJsonDocument::fromJson(decrypted.value.toByteArray()).object();
188 QMap<QString, QString> map;
189 for (auto it = obj.constBegin(); it != obj.constEnd(); it++) {
190 map[it.key()] = it.value().toString();
191 }
192
193 QByteArray bytes;
194 QDataStream ds(&bytes, QIODevice::WriteOnly);
195 ds << map;
196 backend()->writeEntry(walletHandle(), dir, label, bytes, KWallet::Wallet::Map, FDO_APPID);
197 explicit_zero_mem(bytes.data(), bytes.size());
198 } else if (xdgSchema == QStringLiteral("org.kde.KWallet.Password") || secret.mimeType.startsWith(QStringLiteral("text/"))) {
199 auto bytes = decrypted.value.toByteArray();
200 auto str = QString::fromUtf8(bytes);
201 backend()->writePassword(walletHandle(), dir, label, str, FDO_APPID);
202 explicit_zero_mem(bytes.data(), bytes.size());
203 explicit_zero_mem(str.data(), str.size() * sizeof(QChar));
204 } else {
205 auto bytes = decrypted.value.toByteArray();
206 backend()->writeEntry(walletHandle(), dir, label, bytes, KWallet::Wallet::Stream, FDO_APPID);
207 explicit_zero_mem(bytes.data(), bytes.size());
208 }
209 }
210
211 onItemCreated(itemPath);
212
213 return itemPath;
214}
215
216QDBusObjectPath KWalletFreedesktopCollection::Delete()
217{
218 const auto name = walletName();
219
220 const QStringList aliases = fdoService()->readAliasesFor(name);
221 for (const QString &alias : aliases) {
222 fdoService()->removeAlias(alias);
223 }
224
225 backend()->deleteWallet(name);
227 m_service->onCollectionDeleted(fdoObjectPath());
228
229 return QDBusObjectPath("/");
230}
231
232QList<QDBusObjectPath> KWalletFreedesktopCollection::SearchItems(const StrStrMap &attributes)
233{
234 QList<QDBusObjectPath> result;
235
236 for (const auto &entryLoc : m_itemAttribs.matchAttributes(attributes)) {
237 auto *itm = findItemByEntryLocation(entryLoc);
238 if (itm) {
239 result.push_back(itm->fdoObjectPath());
240 }
241 }
242
243 return result;
244}
245
246int KWalletFreedesktopCollection::walletHandle() const
247{
248 return m_handle;
249}
250
251KWalletFreedesktopItem *KWalletFreedesktopCollection::getItemByObjectPath(const QString &objectPath) const
252{
253 const auto found = m_items.find(objectPath);
254 if (found != m_items.end()) {
255 return found->second.get();
256 } else {
257 return nullptr;
258 }
259}
260
261KWalletFreedesktopItem *KWalletFreedesktopCollection::findItemByEntryLocation(const EntryLocation &entryLocation) const
262{
263 const auto uniqLabel = FdoUniqueLabel::fromEntryLocation(entryLocation);
264
265 for (const auto &itemPair : m_items) {
266 auto *item = itemPair.second.get();
267 if (item->uniqueLabel() == uniqLabel) {
268 return item;
269 }
270 }
271
272 return nullptr;
273}
274
275EntryLocation KWalletFreedesktopCollection::makeUniqueEntryLocation(const QString &label)
276{
277 QString dir, name;
278
279 const int slashPos = label.indexOf(QChar::fromLatin1('/'));
280 if (slashPos == -1 || slashPos == label.size() - 1) {
281 dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
282 name = label;
283 } else {
284 dir = label.left(slashPos);
285 name = label.mid(slashPos + 1);
286 }
287
288 int suffix = 0;
289 QString resultName = name;
290 while (backend()->hasEntry(m_handle, dir, resultName, FDO_APPID)) {
291 resultName = FdoUniqueLabel::makeName(name, suffix++);
292 }
293
294 return {dir, resultName};
295}
296
297FdoUniqueLabel KWalletFreedesktopCollection::makeUniqueItemLabel(const QString &label)
298{
299 return makeUniqueEntryLocation(label).toUniqueLabel();
300}
301
302KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(FdoUniqueLabel uniqLabel, const QDBusObjectPath &path)
303{
304 m_items.erase(path.path());
305 auto item = std::make_unique<KWalletFreedesktopItem>(this, std::move(uniqLabel), path);
306 return *m_items.emplace(path.path(), std::move(item)).first->second;
307}
308
309KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(const QString &label, const QDBusObjectPath &path)
310{
311 return pushNewItem(makeUniqueItemLabel(label), path);
312}
313
314KWalletFreedesktopService *KWalletFreedesktopCollection::fdoService() const
315{
316 return m_service;
317}
318
319KSecretD *KWalletFreedesktopCollection::backend() const
320{
321 return fdoService()->backend();
322}
323
324QDBusObjectPath KWalletFreedesktopCollection::fdoObjectPath() const
325{
326 return m_objectPath;
327}
328
329const FdoUniqueLabel &KWalletFreedesktopCollection::uniqueLabel() const
330{
331 return m_uniqueLabel;
332}
333
334QString KWalletFreedesktopCollection::walletName() const
335{
336 return m_uniqueLabel.toName();
337}
338
339void KWalletFreedesktopCollection::onWalletChangeState(int handle)
340{
341 if (handle == m_handle) {
342 return;
343 }
344
345 // this makes the subsequent metadata addition code execute only the
346 // first time an handle is added, right at ctor time
347 if (handle >= 0 && m_handle >= 0) {
348 m_handle = handle;
349 return;
350 }
351
352 m_handle = handle;
353
354 const QStringList folderList = backend()->folderList(m_handle, FDO_APPID);
355 for (const QString &folder : folderList) {
356 const QStringList entries = backend()->entryList(m_handle, folder, FDO_APPID);
357
358 const qulonglong createTime = QDateTime::currentSecsSinceEpoch();
359 for (const auto &entry : entries) {
360 const EntryLocation entryLoc{folder, entry};
361 const auto itm = findItemByEntryLocation(entryLoc);
362 if (!itm) {
363 StrStrMap attr;
364 attr["server"] = folder;
365 attr["user"] = entry;
366 switch (backend()->entryType(m_handle, folder, entry, FDO_APPID)) {
367 case KWallet::Wallet::Stream:
368 attr["type"] = "binary";
369 break;
370 case KWallet::Wallet::Map:
371 attr["type"] = "map";
372 break;
373 case KWallet::Wallet::Password:
374 default:
375 attr["type"] = "plaintext";
376 break;
377 }
378 itemAttributes().newItem(entryLoc);
379 itemAttributes().setParam(entryLoc, FDO_KEY_CREATED, createTime);
380 itemAttributes().setParam(entryLoc, FDO_KEY_MODIFIED, createTime);
381 itemAttributes().setAttributes(entryLoc, attr);
382 auto &newItem = pushNewItem(entryLoc.toUniqueLabel(), nextItemPath());
383 newItem.setAttributes(attr);
384 Q_EMIT ItemCreated(newItem.fdoObjectPath());
385 } else {
386 Q_EMIT ItemChanged(itm->fdoObjectPath());
387 }
388 }
389 }
390}
391
392void KWalletFreedesktopCollection::onItemCreated(const QDBusObjectPath &item)
393{
394 itemAttributes().updateLastModified();
395 Q_EMIT ItemCreated(item);
396
397 QVariantMap props;
398 props[QStringLiteral("Items")] = QVariant::fromValue(items());
399 onPropertiesChanged(props);
400}
401
402void KWalletFreedesktopCollection::onItemChanged(const QDBusObjectPath &item)
403{
404 itemAttributes().updateLastModified();
405 Q_EMIT ItemChanged(item);
406}
407
408void KWalletFreedesktopCollection::onItemDeleted(const QDBusObjectPath &item)
409{
410 itemAttributes().updateLastModified();
411 const auto itemMapPos = m_items.find(item.path());
412 if (itemMapPos == m_items.end()) {
413 return;
414 }
415 auto *itemPtr = itemMapPos->second.get();
416
417 /* This can be called in the context of the item that is currently being
418 * deleted. Therefore we should schedule deletion on the next event loop iteration
419 */
420 itemPtr->setDeleted();
421 itemPtr->deleteLater();
422 itemMapPos->second.release();
423 m_items.erase(itemMapPos);
424
425 Q_EMIT ItemDeleted(item);
426
427 QVariantMap props;
428 props[QStringLiteral("Items")] = QVariant::fromValue(items());
429 onPropertiesChanged(props);
430}
431
432void KWalletFreedesktopCollection::onPropertiesChanged(const QVariantMap &properties)
433{
434 auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
435 auto args = QVariantList();
436 args << QStringLiteral("org.freedesktop.Secret.Collection") << properties << QStringList();
437 msg.setArguments(args);
439}
440
441KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes()
442{
443 return m_itemAttribs;
444}
445
446const KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes() const
447{
448 return m_itemAttribs;
449}
450
451#include "moc_kwalletfreedesktopcollection.cpp"
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardAction id)
KGuiItem properties()
char * data()
qsizetype size() const const
QChar fromLatin1(char c)
qint64 currentSecsSinceEpoch()
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
void unregisterObject(const QString &path, UnregisterMode mode)
const QDBusMessage & message() const const
void sendErrorReply(QDBusError::ErrorType type, const QString &msg) const const
QDBusMessage createSignal(const QString &path, const QString &interface, const QString &name)
QString path() const const
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
const_iterator constBegin() const const
const_iterator constEnd() const const
void push_back(parameter_type value)
iterator end()
iterator find(const Key &key)
T value(const Key &key, const T &defaultValue) const const
Q_EMITQ_EMIT
iterator erase(const_iterator first, const_iterator last)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QVariant fromValue(T &&value)
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.