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 "kwalletd.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{
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 if (xdgSchema == QStringLiteral("org.kde.KWallet.Password") || secret.mimeType.startsWith(QStringLiteral("text/"))) {
185 auto bytes = decrypted.value.toByteArray();
186 auto str = QString::fromUtf8(bytes);
187 backend()->writePassword(walletHandle(), dir, label, str, FDO_APPID);
188 explicit_zero_mem(bytes.data(), bytes.size());
189 explicit_zero_mem(str.data(), str.size() * sizeof(QChar));
190 } else {
191 auto bytes = decrypted.value.toByteArray();
192 backend()->writeEntry(walletHandle(), dir, label, bytes, KWallet::Wallet::Stream, FDO_APPID);
193 explicit_zero_mem(bytes.data(), bytes.size());
194 }
195 }
196
197 onItemCreated(itemPath);
198
199 return itemPath;
200}
201
202QDBusObjectPath KWalletFreedesktopCollection::Delete()
203{
204 const auto name = walletName();
205
206 const QStringList aliases = fdoService()->readAliasesFor(name);
207 for (const QString &alias : aliases) {
208 fdoService()->removeAlias(alias);
209 }
210
211 backend()->deleteWallet(name);
213 m_service->onCollectionDeleted(fdoObjectPath());
214
215 return QDBusObjectPath("/");
216}
217
218QList<QDBusObjectPath> KWalletFreedesktopCollection::SearchItems(const StrStrMap &attributes)
219{
221
222 for (const auto &entryLoc : m_itemAttribs.matchAttributes(attributes)) {
223 auto *itm = findItemByEntryLocation(entryLoc);
224 if (itm) {
225 result.push_back(itm->fdoObjectPath());
226 }
227 }
228
229 return result;
230}
231
232int KWalletFreedesktopCollection::walletHandle() const
233{
234 return m_handle;
235}
236
237KWalletFreedesktopItem *KWalletFreedesktopCollection::getItemByObjectPath(const QString &objectPath) const
238{
239 const auto found = m_items.find(objectPath);
240 if (found != m_items.end()) {
241 return found->second.get();
242 } else {
243 return nullptr;
244 }
245}
246
247KWalletFreedesktopItem *KWalletFreedesktopCollection::findItemByEntryLocation(const EntryLocation &entryLocation) const
248{
249 const auto uniqLabel = FdoUniqueLabel::fromEntryLocation(entryLocation);
250
251 for (const auto &itemPair : m_items) {
252 auto *item = itemPair.second.get();
253 if (item->uniqueLabel() == uniqLabel) {
254 return item;
255 }
256 }
257
258 return nullptr;
259}
260
261EntryLocation KWalletFreedesktopCollection::makeUniqueEntryLocation(const QString &label)
262{
264
265 const int slashPos = label.indexOf(QChar::fromLatin1('/'));
266 if (slashPos == -1 || slashPos == label.size() - 1) {
267 dir = QStringLiteral(FDO_SECRETS_DEFAULT_DIR);
268 name = label;
269 } else {
270 dir = label.left(slashPos);
271 name = label.mid(slashPos + 1);
272 }
273
274 int suffix = 0;
275 QString resultName = name;
276 while (backend()->hasEntry(m_handle, dir, resultName, FDO_APPID)) {
277 resultName = FdoUniqueLabel::makeName(name, suffix++);
278 }
279
280 return {dir, resultName};
281}
282
283FdoUniqueLabel KWalletFreedesktopCollection::makeUniqueItemLabel(const QString &label)
284{
285 return makeUniqueEntryLocation(label).toUniqueLabel();
286}
287
288KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(FdoUniqueLabel uniqLabel, const QDBusObjectPath &path)
289{
290 m_items.erase(path.path());
291 auto item = std::make_unique<KWalletFreedesktopItem>(this, std::move(uniqLabel), path);
292 return *m_items.emplace(path.path(), std::move(item)).first->second;
293}
294
295KWalletFreedesktopItem &KWalletFreedesktopCollection::pushNewItem(const QString &label, const QDBusObjectPath &path)
296{
297 return pushNewItem(makeUniqueItemLabel(label), path);
298}
299
300KWalletFreedesktopService *KWalletFreedesktopCollection::fdoService() const
301{
302 return m_service;
303}
304
305KWalletD *KWalletFreedesktopCollection::backend() const
306{
307 return fdoService()->backend();
308}
309
310QDBusObjectPath KWalletFreedesktopCollection::fdoObjectPath() const
311{
312 return m_objectPath;
313}
314
315const FdoUniqueLabel &KWalletFreedesktopCollection::uniqueLabel() const
316{
317 return m_uniqueLabel;
318}
319
320QString KWalletFreedesktopCollection::walletName() const
321{
322 return m_uniqueLabel.toName();
323}
324
325void KWalletFreedesktopCollection::onWalletChangeState(int handle)
326{
327 if (handle == m_handle) {
328 return;
329 }
330
331 if (handle >= 0 && m_handle >= 0) {
332 m_handle = handle;
333 return;
334 }
335
336 m_handle = handle;
337
338 if (m_handle < 0 || !m_items.empty()) {
339 return;
340 }
341
342 const QStringList folderList = backend()->folderList(m_handle, FDO_APPID);
343 for (const QString &folder : folderList) {
344 const QStringList entries = backend()->entryList(m_handle, folder, FDO_APPID);
345
346 for (const auto &entry : entries) {
347 const EntryLocation entryLoc{folder, entry};
348 const auto itm = findItemByEntryLocation(entryLoc);
349 if (!itm) {
350 auto &newItem = pushNewItem(entryLoc.toUniqueLabel(), nextItemPath());
351 Q_EMIT ItemChanged(newItem.fdoObjectPath());
352 } else {
353 Q_EMIT ItemChanged(itm->fdoObjectPath());
354 }
355 }
356 }
357}
358
359void KWalletFreedesktopCollection::onItemCreated(const QDBusObjectPath &item)
360{
361 itemAttributes().updateLastModified();
362 Q_EMIT ItemCreated(item);
363
364 QVariantMap props;
365 props[QStringLiteral("Items")] = QVariant::fromValue(items());
366 onPropertiesChanged(props);
367}
368
369void KWalletFreedesktopCollection::onItemChanged(const QDBusObjectPath &item)
370{
371 itemAttributes().updateLastModified();
372 Q_EMIT ItemChanged(item);
373}
374
375void KWalletFreedesktopCollection::onItemDeleted(const QDBusObjectPath &item)
376{
377 itemAttributes().updateLastModified();
378 const auto itemMapPos = m_items.find(item.path());
379 if (itemMapPos == m_items.end()) {
380 return;
381 }
382 auto *itemPtr = itemMapPos->second.get();
383
384 /* This can be called in the context of the item that is currently being
385 * deleted. Therefore we should schedule deletion on the next event loop iteration
386 */
387 itemPtr->setDeleted();
388 itemPtr->deleteLater();
389 itemMapPos->second.release();
390 m_items.erase(itemMapPos);
391
392 Q_EMIT ItemDeleted(item);
393
394 QVariantMap props;
395 props[QStringLiteral("Items")] = QVariant::fromValue(items());
396 onPropertiesChanged(props);
397}
398
399void KWalletFreedesktopCollection::onPropertiesChanged(const QVariantMap &properties)
400{
401 auto msg = QDBusMessage::createSignal(fdoObjectPath().path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("PropertiesChanged"));
402 auto args = QVariantList();
403 args << QStringLiteral("org.freedesktop.Secret.Collection") << properties << QStringList();
404 msg.setArguments(args);
406}
407
408KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes()
409{
410 return m_itemAttribs;
411}
412
413const KWalletFreedesktopAttributes &KWalletFreedesktopCollection::itemAttributes() const
414{
415 return m_itemAttribs;
416}
417
418#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)
KGuiItem properties()
QString label(StandardShortcut id)
QString name(StandardShortcut id)
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
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
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)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:05 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.