KIO

kpasswdserver.cpp
1/*
2 This file is part of the KDE Password Server
3 SPDX-FileCopyrightText: 2002 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2005 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2012 Dawit Alemayehu <adawit@kde.org>
6 SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
7
8 SPDX-License-Identifier: GPL-2.0-only
9*/
10
11// KDE Password Server
12
13#include "kpasswdserver.h"
14
15#include "kpasswdserveradaptor.h"
16
17#include <KLocalizedString>
18#include <KMessageDialog>
19#include <KPasswordDialog>
20#include <kwindowsystem.h>
21
22#ifdef HAVE_KF6WALLET
23#include <KWallet>
24#endif
25
26#include <QPushButton>
27#include <QTimer>
28#include <ctime>
29
30#include "../gui/config-kiogui.h"
31
32#if HAVE_X11
33#include <KUserTimestamp>
34#include <KX11Extras>
35#endif
36
37Q_LOGGING_CATEGORY(category, "kf.kio.kpasswdserver", QtInfoMsg)
38
39static const char s_domain[] = "domain";
40static const char s_anonymous[] = "anonymous";
41static const char s_bypassCacheAndKwallet[] = "bypass-cache-and-kwallet";
42static const char s_skipCachingOnQuery[] = "skip-caching-on-query";
43static const char s_hideUsernameInput[] = "hide-username-line";
44static const char s_usernameContextHelp[] = "username-context-help";
45
46static qlonglong getRequestId()
47{
48 static qlonglong nextRequestId = 0;
49 return nextRequestId++;
50}
51
52bool KPasswdServer::AuthInfoContainer::Sorter::operator()(const AuthInfoContainer &n1, const AuthInfoContainer &n2) const
53{
54 const int l1 = n1.directory.length();
55 const int l2 = n2.directory.length();
56 return l1 < l2;
57}
58
59KPasswdServer::KPasswdServer(QObject *parent, const QList<QVariant> &)
61{
63
64 m_seqNr = 0;
65 m_wallet = nullptr;
66 m_walletDisabled = false;
67
68 KPasswdServerAdaptor *adaptor = new KPasswdServerAdaptor(this);
69 // connect signals to the adaptor
70 connect(this, &KPasswdServer::checkAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::checkAuthInfoAsyncResult);
71 connect(this, &KPasswdServer::queryAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::queryAuthInfoAsyncResult);
72
73 connect(this, &KDEDModule::windowUnregistered, this, &KPasswdServer::removeAuthForWindowId);
74
75#if HAVE_X11
76 connect(KX11Extras::self(), &KX11Extras::windowRemoved, this, &KPasswdServer::windowRemoved);
77#endif
78}
79
80KPasswdServer::~KPasswdServer()
81{
82 // TODO: what about clients waiting for requests? will they just
83 // notice kpasswdserver is gone from the dbus?
84 qDeleteAll(m_authPending);
85 qDeleteAll(m_authWait);
86 qDeleteAll(m_authDict);
87 qDeleteAll(m_authInProgress);
88 qDeleteAll(m_authRetryInProgress);
89
90#ifdef HAVE_KF6WALLET
91 delete m_wallet;
92#endif
93}
94
95#ifdef HAVE_KF6WALLET
96
97// Helper - returns the wallet key to use for read/store/checking for existence.
98static QString makeWalletKey(const QString &key, const QString &realm)
99{
100 return realm.isEmpty() ? key : key + QLatin1Char('-') + realm;
101}
102
103// Helper for storeInWallet/readFromWallet
104static QString makeMapKey(const char *key, int entryNumber)
105{
106 QString str = QLatin1String(key);
107 if (entryNumber > 1) {
108 str += QLatin1Char('-') + QString::number(entryNumber);
109 }
110 return str;
111}
112
113static bool storeInWallet(KWallet::Wallet *wallet, const QString &key, const KIO::AuthInfo &info)
114{
117 return false;
118 }
119 }
121 // Before saving, check if there's already an entry with this login.
122 // If so, replace it (with the new password). Otherwise, add a new entry.
124 int entryNumber = 1;
125 Map map;
126 QString walletKey = makeWalletKey(key, info.realmValue);
127 qCDebug(category) << "walletKey =" << walletKey << " reading existing map";
128 if (wallet->readMap(walletKey, map) == 0) {
129 Map::ConstIterator end = map.constEnd();
130 Map::ConstIterator it = map.constFind(QStringLiteral("login"));
131 while (it != end) {
132 if (it.value() == info.username) {
133 break; // OK, overwrite this entry
134 }
135 it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber));
136 }
137 // If no entry was found, create a new entry - entryNumber is set already.
138 }
139 const QString loginKey = makeMapKey("login", entryNumber);
140 const QString passwordKey = makeMapKey("password", entryNumber);
141 qCDebug(category) << "writing to " << loginKey << "," << passwordKey;
142 // note the overwrite=true by default
143 map.insert(loginKey, info.username);
144 map.insert(passwordKey, info.password);
145 wallet->writeMap(walletKey, map);
146 return true;
147}
148
149static bool readFromWallet(KWallet::Wallet *wallet,
150 const QString &key,
151 const QString &realm,
152 QString &username,
153 QString &password,
154 bool userReadOnly,
155 QMap<QString, QString> &knownLogins)
156{
157 // qCDebug(category) << "key =" << key << " username =" << username << " password =" /*<< password*/
158 // << " userReadOnly =" << userReadOnly << " realm =" << realm;
161
163 if (wallet->readMap(makeWalletKey(key, realm), map) == 0) {
165 int entryNumber = 1;
166 Map::ConstIterator end = map.constEnd();
167 Map::ConstIterator it = map.constFind(QStringLiteral("login"));
168 while (it != end) {
169 // qCDebug(category) << "found " << it.key() << "=" << it.value();
170 Map::ConstIterator pwdIter = map.constFind(makeMapKey("password", entryNumber));
171 if (pwdIter != end) {
172 if (it.value() == username) {
173 password = pwdIter.value();
174 }
175 knownLogins.insert(it.value(), pwdIter.value());
176 }
177
178 it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber));
179 }
180 // qCDebug(category) << knownLogins.count() << " known logins";
181
182 if (!userReadOnly && !knownLogins.isEmpty() && username.isEmpty()) {
183 // Pick one, any one...
184 username = knownLogins.begin().key();
185 password = knownLogins.begin().value();
186 // qCDebug(category) << "picked the first one:" << username;
187 }
188
189 return true;
190 }
191 }
192 return false;
193}
194
195#endif
196
197bool KPasswdServer::hasPendingQuery(const QString &key, const KIO::AuthInfo &info)
198{
199 const QString path2(info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1));
200 for (const Request *request : std::as_const(m_authPending)) {
201 if (request->key != key) {
202 continue;
203 }
204
205 if (info.verifyPath) {
206 const QString path1(request->info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1));
207 if (!path2.startsWith(path1)) {
208 continue;
209 }
210 }
211
212 return true;
213 }
214
215 return false;
216}
217
218// deprecated method, not used anymore. TODO KF6: REMOVE
219QByteArray KPasswdServer::checkAuthInfo(const QByteArray &data, qlonglong windowId, qlonglong usertime)
220{
221 KIO::AuthInfo info;
222 QDataStream stream(data);
223 stream >> info;
224#if HAVE_X11
225 if (usertime != 0) {
226 KUserTimestamp::updateUserTimestamp(usertime);
227 }
228#endif
229
230 // if the check depends on a pending query, delay it
231 // until that query is finished.
232 const QString key(createCacheKey(info));
233 if (hasPendingQuery(key, info)) {
234 setDelayedReply(true);
235 Request *pendingCheck = new Request;
236 pendingCheck->isAsync = false;
237 if (calledFromDBus()) {
238 pendingCheck->transaction = message();
239 }
240 pendingCheck->key = key;
241 pendingCheck->info = info;
242 m_authWait.append(pendingCheck);
243 return data; // return value will be ignored
244 }
245
246 // qCDebug(category) << "key =" << key << "user =" << info.username << "windowId =" << windowId;
247 const AuthInfoContainer *result = findAuthInfoItem(key, info);
248 if (!result || result->isCanceled) {
249#ifdef HAVE_KF6WALLET
250 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty())
252 QMap<QString, QString> knownLogins;
253 if (openWallet(windowId)) {
254 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) {
255 info.setModified(true);
256 // fall through
257 }
258 }
259 } else {
260 info.setModified(false);
261 }
262#else
263 info.setModified(false);
264#endif
265 } else {
266 qCDebug(category) << "Found cached authentication for" << key;
267 updateAuthExpire(key, result, windowId, false);
268 copyAuthInfo(result, info);
269 }
270
271 QByteArray data2;
272 QDataStream stream2(&data2, QIODevice::WriteOnly);
273 stream2 << info;
274 return data2;
275}
276
277qlonglong KPasswdServer::checkAuthInfoAsync(KIO::AuthInfo info, qlonglong windowId, qlonglong usertime)
278{
279#if HAVE_X11
280 if (usertime != 0) {
281 KUserTimestamp::updateUserTimestamp(usertime);
282 }
283#endif
284
285 // send the request id back to the client
286 qlonglong requestId = getRequestId();
287 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId;
288 if (calledFromDBus()) {
289 QDBusMessage reply(message().createReply(requestId));
291 }
292
293 // if the check depends on a pending query, delay it
294 // until that query is finished.
295 const QString key(createCacheKey(info));
296 if (hasPendingQuery(key, info)) {
297 Request *pendingCheck = new Request;
298 pendingCheck->isAsync = true;
299 pendingCheck->requestId = requestId;
300 pendingCheck->key = key;
301 pendingCheck->info = info;
302 m_authWait.append(pendingCheck);
303 return 0; // ignored as we already sent a reply
304 }
305
306 const AuthInfoContainer *result = findAuthInfoItem(key, info);
307 if (!result || result->isCanceled) {
308#ifdef HAVE_KF6WALLET
309 if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty())
311 QMap<QString, QString> knownLogins;
312 if (openWallet(windowId)) {
313 if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) {
314 info.setModified(true);
315 // fall through
316 }
317 }
318 } else {
319 info.setModified(false);
320 }
321#else
322 info.setModified(false);
323#endif
324 } else {
325 // qCDebug(category) << "Found cached authentication for" << key;
326 updateAuthExpire(key, result, windowId, false);
327 copyAuthInfo(result, info);
328 }
329
330 Q_EMIT checkAuthInfoAsyncResult(requestId, m_seqNr, info);
331 return 0; // ignored
332}
333
334// deprecated method, not used anymore. TODO KF6: REMOVE
335QByteArray KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime)
336{
337 KIO::AuthInfo info;
338 QDataStream stream(data);
339 stream >> info;
340
341 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg;
342
343 if (!info.password.isEmpty()) { // should we really allow the caller to pre-fill the password?
344 qCDebug(category) << "password was set by caller";
345 }
346#if HAVE_X11
347 if (usertime != 0) {
348 KUserTimestamp::updateUserTimestamp(usertime);
349 }
350#endif
351
352 const QString key(createCacheKey(info));
353 Request *request = new Request;
354 setDelayedReply(true);
355 request->isAsync = false;
356 request->transaction = message();
357 request->key = key;
358 request->info = info;
359 request->windowId = windowId;
360 request->seqNr = seqNr;
361 if (errorMsg == QLatin1String("<NoAuthPrompt>")) {
362 request->errorMsg.clear();
363 request->prompt = false;
364 } else {
365 request->errorMsg = errorMsg;
366 request->prompt = true;
367 }
368 m_authPending.append(request);
369
370 if (m_authPending.count() == 1) {
371 QTimer::singleShot(0, this, &KPasswdServer::processRequest);
372 }
373
374 return QByteArray(); // return value is going to be ignored
375}
376
377qlonglong KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime)
378{
379 qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg;
380
381 if (!info.password.isEmpty()) {
382 qCDebug(category) << "password was set by caller";
383 }
384#if HAVE_X11
385 if (usertime != 0) {
386 KUserTimestamp::updateUserTimestamp(usertime);
387 }
388#endif
389
390 const QString key(createCacheKey(info));
391 Request *request = new Request;
392 request->isAsync = true;
393 request->requestId = getRequestId();
394 request->key = key;
395 request->info = info;
396 request->windowId = windowId;
397 request->seqNr = seqNr;
398 if (errorMsg == QLatin1String("<NoAuthPrompt>")) {
399 request->errorMsg.clear();
400 request->prompt = false;
401 } else {
402 request->errorMsg = errorMsg;
403 request->prompt = true;
404 }
405 m_authPending.append(request);
406
407 if (m_authPending.count() == 1) {
408 QTimer::singleShot(0, this, &KPasswdServer::processRequest);
409 }
410
411 return request->requestId;
412}
413
414void KPasswdServer::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId)
415{
416 qCDebug(category) << "User =" << info.username << ", Realm =" << info.realmValue << ", WindowId =" << windowId;
417 if (!info.keepPassword) {
418 qWarning() << "This KIO worker is caching a password in KWallet even though the user didn't ask for it!";
419 }
420 const QString key(createCacheKey(info));
421
422 m_seqNr++;
423
424#ifdef HAVE_KF6WALLET
425 if (!m_walletDisabled && openWallet(windowId) && storeInWallet(m_wallet, key, info)) {
426 // Since storing the password in the wallet succeeded, make sure the
427 // password information is stored in memory only for the duration the
428 // windows associated with it are still around.
429 KIO::AuthInfo authToken(info);
430 authToken.keepPassword = false;
431 addAuthInfoItem(key, authToken, windowId, m_seqNr, false);
432 return;
433 }
434#endif
435
436 addAuthInfoItem(key, info, windowId, m_seqNr, false);
437}
438
439// deprecated method, not used anymore. TODO KF6: REMOVE
440void KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId)
441{
442 KIO::AuthInfo info;
443 QDataStream stream(data);
444 stream >> info;
445 addAuthInfo(info, windowId);
446}
447
448void KPasswdServer::removeAuthInfo(const QString &host, const QString &protocol, const QString &user)
449{
450 qCDebug(category) << protocol << host << user;
451
453 while (dictIterator.hasNext()) {
454 dictIterator.next();
455
456 const AuthInfoContainerList *authList = dictIterator.value();
457 if (!authList) {
458 continue;
459 }
460
461 for (const AuthInfoContainer &current : *authList) {
462 qCDebug(category) << "Evaluating: " << current.info.url.scheme() << current.info.url.host() << current.info.username;
463 if (current.info.url.scheme() == protocol && current.info.url.host() == host && (current.info.username == user || user.isEmpty())) {
464 qCDebug(category) << "Removing this entry";
465 removeAuthInfoItem(dictIterator.key(), current.info); // warning, this can modify m_authDict!
466 }
467 }
468 }
469}
470
471#ifdef HAVE_KF6WALLET
472bool KPasswdServer::openWallet(qlonglong windowId)
473{
474 if (m_wallet && !m_wallet->isOpen()) { // forced closed
475 delete m_wallet;
476 m_wallet = nullptr;
477 }
478 if (!m_wallet) {
479 m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), static_cast<WId>(windowId));
480 }
481 return m_wallet != nullptr;
482}
483#endif
484
485void KPasswdServer::processRequest()
486{
487 if (m_authPending.isEmpty()) {
488 return;
489 }
490
491 std::unique_ptr<Request> request(m_authPending.takeFirst());
492
493 // Prevent multiple prompts originating from the same window or the same
494 // key (server address).
495 const QString windowIdStr = QString::number(request->windowId);
496 if (m_authPrompted.contains(windowIdStr) || m_authPrompted.contains(request->key)) {
497 m_authPending.prepend(request.release()); // put it back.
498 return;
499 }
500
501 m_authPrompted.append(windowIdStr);
502 m_authPrompted.append(request->key);
503
504 KIO::AuthInfo &info = request->info;
505
506 // NOTE: If info.username is empty and info.url.userName() is not, set
507 // info.username to info.url.userName() to ensure proper caching. See
508 // note passwordDialogDone.
509 if (info.username.isEmpty() && !info.url.userName().isEmpty()) {
510 info.username = info.url.userName();
511 }
512 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool();
513
514 const AuthInfoContainer *result = findAuthInfoItem(request->key, request->info);
515 qCDebug(category) << "key=" << request->key << ", user=" << info.username << "seqNr: request=" << request->seqNr
516 << ", result=" << (result ? result->seqNr : -1);
517
518 if (!bypassCacheAndKWallet && result && (request->seqNr < result->seqNr)) {
519 qCDebug(category) << "auto retry!";
520 if (result->isCanceled) {
521 info.setModified(false);
522 } else {
523 updateAuthExpire(request->key, result, request->windowId, false);
524 copyAuthInfo(result, info);
525 }
526 } else {
527 m_seqNr++;
528 if (result && !request->errorMsg.isEmpty()) {
529 const QString prompt = request->errorMsg.trimmed() + QLatin1Char('\n') + i18n("Do you want to retry?");
530
533 dlg->setWindowTitle(i18n("Retry Authentication"));
534 dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password")));
535 dlg->setObjectName(QStringLiteral("warningOKCancel"));
536 KGuiItem retryButton(i18nc("@action:button filter-continue", "Retry"));
537
538 dlg->setButtons(retryButton);
539
540 connect(dlg, &QDialog::finished, this, [this, dlg](int result) {
541 retryDialogDone(result, dlg);
542 });
543
545 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId);
546
547 qCDebug(category) << "Calling open on retry dialog" << dlg;
548 m_authRetryInProgress.insert(dlg, request.release());
549 dlg->open();
550 return;
551 }
552
553 if (request->prompt) {
554 showPasswordDialog(request.release());
555 return;
556 } else {
557 if (!bypassCacheAndKWallet && request->prompt) {
558 addAuthInfoItem(request->key, info, 0, m_seqNr, true);
559 }
560 info.setModified(false);
561 }
562 }
563
564 sendResponse(request.get());
565}
566
567QString KPasswdServer::createCacheKey(const KIO::AuthInfo &info)
568{
569 if (!info.url.isValid()) {
570 // Note that a null key will break findAuthInfoItem later on...
571 qCWarning(category) << "createCacheKey: invalid URL " << info.url;
572 return QString();
573 }
574
575 // Generate the basic key sequence.
576 QString key = info.url.scheme();
577 key += QLatin1Char('-');
578 if (!info.url.userName().isEmpty()) {
579 key += info.url.userName() + QLatin1Char('@');
580 }
581 key += info.url.host();
582 int port = info.url.port();
583 if (port) {
584 key += QLatin1Char(':') + QString::number(port);
585 }
586
587 return key;
588}
589
590void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo &info)
591{
592 info = i->info;
593 info.setModified(true);
594}
595
596const KPasswdServer::AuthInfoContainer *KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
597{
598 // qCDebug(category) << "key=" << key << ", user=" << info.username;
599
600 AuthInfoContainerList *authList = m_authDict.value(key);
601 if (authList) {
602 QString path2 = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1);
603 auto it = authList->begin();
604 while (it != authList->end()) {
605 const AuthInfoContainer &current = (*it);
606 if (current.expire == AuthInfoContainer::expTime && static_cast<qulonglong>(time(nullptr)) > current.expireTime) {
607 it = authList->erase(it);
608 continue;
609 }
610
611 if (info.verifyPath) {
612 const QString path1 = current.directory;
613 if (path2.startsWith(path1) && (info.username.isEmpty() || info.username == current.info.username)) {
614 return &current;
615 }
616 } else {
617 if (current.info.realmValue == info.realmValue && (info.username.isEmpty() || info.username == current.info.username)) {
618 return &current; // TODO: Update directory info
619 }
620 }
621
622 ++it;
623 }
624 }
625 return nullptr;
626}
627
628void KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info)
629{
630 AuthInfoContainerList *authList = m_authDict.value(key);
631 if (!authList) {
632 return;
633 }
634
635 auto it = authList->begin();
636 while (it != authList->end()) {
637 if ((*it).info.realmValue == info.realmValue) {
638 it = authList->erase(it);
639 } else {
640 ++it;
641 }
642 }
643 if (authList->isEmpty()) {
644 delete m_authDict.take(key);
645 }
646}
647
648void KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled)
649{
650 qCDebug(category) << "key=" << key << "window-id=" << windowId << "username=" << info.username << "realm=" << info.realmValue << "seqNr=" << seqNr
651 << "keepPassword?" << info.keepPassword << "canceled?" << canceled;
652 AuthInfoContainerList *authList = m_authDict.value(key);
653 if (!authList) {
654 authList = new AuthInfoContainerList;
655 m_authDict.insert(key, authList);
656 }
657 bool found = false;
658 AuthInfoContainer authItem;
659 auto it = authList->begin();
660 while (it != authList->end()) {
661 if ((*it).info.realmValue == info.realmValue) {
662 authItem = (*it);
663 it = authList->erase(it);
664 found = true;
665 break;
666 } else {
667 ++it;
668 }
669 }
670
671 if (!found) {
672 qCDebug(category) << "Creating AuthInfoContainer";
673 authItem.expire = AuthInfoContainer::expTime;
674 }
675
676 authItem.info = info;
677 authItem.directory = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1);
678 authItem.seqNr = seqNr;
679 authItem.isCanceled = canceled;
680
681 updateAuthExpire(key, &authItem, windowId, (info.keepPassword && !canceled));
682
683 // Insert into list, keep the list sorted "longest path" first.
684 authList->append(authItem);
685 std::sort(authList->begin(), authList->end(), AuthInfoContainer::Sorter());
686}
687
688void KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep)
689{
690 AuthInfoContainer *current = const_cast<AuthInfoContainer *>(auth);
691 Q_ASSERT(current);
692
693 qCDebug(category) << "key=" << key << "expire=" << current->expire << "window-id=" << windowId << "keep=" << keep;
694
695 if (keep && !windowId) {
696 current->expire = AuthInfoContainer::expNever;
697 } else if (windowId && (current->expire != AuthInfoContainer::expNever)) {
698 current->expire = AuthInfoContainer::expWindowClose;
699 if (!current->windowList.contains(windowId)) {
700 current->windowList.append(windowId);
701 }
702 } else if (current->expire == AuthInfoContainer::expTime) {
703 current->expireTime = time(nullptr) + 10;
704 }
705
706 // Update mWindowIdList
707 if (windowId) {
708 QStringList &keysChanged = mWindowIdList[windowId]; // find or insert
709 if (!keysChanged.contains(key)) {
710 keysChanged.append(key);
711 }
712 }
713}
714
715void KPasswdServer::removeAuthForWindowId(qlonglong windowId)
716{
717 const QStringList keysChanged = mWindowIdList.value(windowId);
718 for (const QString &key : keysChanged) {
719 AuthInfoContainerList *authList = m_authDict.value(key);
720 if (!authList) {
721 continue;
722 }
723
725 while (it.hasNext()) {
726 AuthInfoContainer &current = it.next();
727 if (current.expire == AuthInfoContainer::expWindowClose) {
728 if (current.windowList.removeAll(windowId) && current.windowList.isEmpty()) {
729 it.remove();
730 }
731 }
732 }
733 }
734}
735
736void KPasswdServer::showPasswordDialog(KPasswdServer::Request *request)
737{
738 KIO::AuthInfo &info = request->info;
739 QString username = info.username;
740 QString password = info.password;
741 bool hasWalletData = false;
742 QMap<QString, QString> knownLogins;
743
744#ifdef HAVE_KF6WALLET
745 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool();
746 if (!bypassCacheAndKWallet && (username.isEmpty() || password.isEmpty()) && !m_walletDisabled
749 makeWalletKey(request->key, info.realmValue))) {
750 // no login+pass provided, check if kwallet has one
751 if (openWallet(request->windowId)) {
752 hasWalletData = readFromWallet(m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins);
753 }
754 }
755#endif
756
757 // assemble dialog-flags
759
760 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) {
761 dialogFlags |= KPasswordDialog::ShowDomainLine;
762 if (info.getExtraFieldFlags(QString::fromLatin1(s_domain)) & KIO::AuthInfo::ExtraFieldReadOnly) {
763 dialogFlags |= KPasswordDialog::DomainReadOnly;
764 }
765 }
766
767 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) {
769 }
770
771 if (!info.getExtraField(QString::fromLatin1(s_hideUsernameInput)).toBool()) {
773 }
774
775#ifdef HAVE_KF6WALLET
776 // If wallet is not enabled and the caller explicitly requested for it,
777 // do not show the keep password checkbox.
780 }
781#endif
782
783 // instantiate dialog
784 qCDebug(category) << "Widget for" << request->windowId << QWidget::find(request->windowId);
785
786 KPasswordDialog *dlg = new KPasswordDialog(nullptr, dialogFlags);
788
789 connect(dlg, &QDialog::finished, this, [this, dlg](int result) {
790 passwordDialogDone(result, dlg);
791 });
792
793 dlg->setPrompt(info.prompt);
794 dlg->setUsername(username);
795 if (info.caption.isEmpty()) {
796 dlg->setWindowTitle(i18n("Authentication Dialog"));
797 } else {
798 dlg->setWindowTitle(info.caption);
799 }
800
801 if (!info.comment.isEmpty()) {
802 dlg->addCommentLine(info.commentLabel, info.comment);
803 }
804
805 if (!password.isEmpty()) {
806 dlg->setPassword(password);
807 }
808
809 if (info.readOnly) {
810 dlg->setUsernameReadOnly(true);
811 } else {
812 dlg->setKnownLogins(knownLogins);
813 }
814
815 if (hasWalletData) {
816 dlg->setKeepPassword(true);
817 }
818
819 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) {
820 dlg->setDomain(info.getExtraField(QString::fromLatin1(s_domain)).toString());
821 }
822
823 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid() && password.isEmpty() && username.isEmpty()) {
824 dlg->setAnonymousMode(info.getExtraField(QString::fromLatin1(s_anonymous)).toBool());
825 }
826
827 const QVariant userContextHelp = info.getExtraField(QString::fromLatin1(s_usernameContextHelp));
828 if (userContextHelp.isValid()) {
829 dlg->setUsernameContextHelp(userContextHelp.toString());
830 }
831
832#ifndef Q_OS_MACOS
834 KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId);
835#endif
836
837 qCDebug(category) << "Showing password dialog" << dlg << ", window-id=" << request->windowId;
838 m_authInProgress.insert(dlg, request);
839 dlg->open();
840}
841
842void KPasswdServer::sendResponse(KPasswdServer::Request *request)
843{
844 Q_ASSERT(request);
845 if (!request) {
846 return;
847 }
848
849 qCDebug(category) << "key=" << request->key;
850 if (request->isAsync) {
851 Q_EMIT queryAuthInfoAsyncResult(request->requestId, m_seqNr, request->info);
852 } else {
853 QByteArray replyData;
854 QDataStream stream2(&replyData, QIODevice::WriteOnly);
855 stream2 << request->info;
856 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)}));
857 }
858
859 // Check all requests in the wait queue.
860 Request *waitRequest;
861 QMutableListIterator<Request *> it(m_authWait);
862 while (it.hasNext()) {
863 waitRequest = it.next();
864
865 if (!hasPendingQuery(waitRequest->key, waitRequest->info)) {
866 const AuthInfoContainer *result = findAuthInfoItem(waitRequest->key, waitRequest->info);
867 QByteArray replyData;
868
869 QDataStream stream2(&replyData, QIODevice::WriteOnly);
870
871 KIO::AuthInfo rcinfo;
872 if (!result || result->isCanceled) {
873 waitRequest->info.setModified(false);
874 stream2 << waitRequest->info;
875 } else {
876 updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false);
877 copyAuthInfo(result, rcinfo);
878 stream2 << rcinfo;
879 }
880
881 if (waitRequest->isAsync) {
882 Q_EMIT checkAuthInfoAsyncResult(waitRequest->requestId, m_seqNr, rcinfo);
883 } else {
884 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList{QVariant(replyData), QVariant(m_seqNr)}));
885 }
886
887 delete waitRequest;
888 it.remove();
889 }
890 }
891
892 // Re-enable password request processing for the current window id again.
893 m_authPrompted.removeAll(QString::number(request->windowId));
894 m_authPrompted.removeAll(request->key);
895
896 if (!m_authPending.isEmpty()) {
897 QTimer::singleShot(0, this, &KPasswdServer::processRequest);
898 }
899}
900
901void KPasswdServer::passwordDialogDone(int result, KPasswordDialog *sender)
902{
903 std::unique_ptr<Request> request(m_authInProgress.take(sender));
904 Q_ASSERT(request); // request should never be nullptr.
905
906 if (request) {
907 KIO::AuthInfo &info = request->info;
908 const bool bypassCacheAndKWallet = info.getExtraField(QString::fromLatin1(s_bypassCacheAndKwallet)).toBool();
909
910 qCDebug(category) << "dialog result=" << result << ", bypassCacheAndKWallet?" << bypassCacheAndKWallet;
911 if (sender && result == QDialog::Accepted) {
912 info.username = sender->username();
913 info.password = sender->password();
914 info.keepPassword = sender->keepPassword();
915
916 if (info.getExtraField(QString::fromLatin1(s_domain)).isValid()) {
917 info.setExtraField(QString::fromLatin1(s_domain), sender->domain());
918 }
919 if (info.getExtraField(QString::fromLatin1(s_anonymous)).isValid()) {
920 info.setExtraField(QString::fromLatin1(s_anonymous), sender->anonymousMode());
921 }
922
923 // When the user checks "keep password", that means:
924 // * if the wallet is enabled, store it there for long-term, and in kpasswdserver
925 // only for the duration of the window (#92928)
926 // * otherwise store in kpasswdserver for the duration of the KDE session.
927 if (!bypassCacheAndKWallet) {
928 /*
929 NOTE: The following code changes the key under which the auth
930 info is stored in memory if the request url contains a username.
931 e.g. "ftp://user@localhost", but the user changes that username
932 in the password dialog.
933
934 Since the key generated to store the credential contains the
935 username from the request URL, the key must be updated on such
936 changes. Otherwise, the key will not be found on subsequent
937 requests and the user will be end up being prompted over and
938 over to re-enter the password unnecessarily.
939 */
940 if (!info.url.userName().isEmpty() && info.username != info.url.userName()) {
941 const QString oldKey(request->key);
942 removeAuthInfoItem(oldKey, info);
943 info.url.setUserName(info.username);
944 request->key = createCacheKey(info);
945 updateCachedRequestKey(m_authPending, oldKey, request->key);
946 updateCachedRequestKey(m_authWait, oldKey, request->key);
947 }
948
949#ifdef HAVE_KF6WALLET
950 const bool skipAutoCaching = info.getExtraField(QString::fromLatin1(s_skipCachingOnQuery)).toBool();
951 if (!skipAutoCaching && info.keepPassword && openWallet(request->windowId)) {
952 if (storeInWallet(m_wallet, request->key, info)) {
953 // password is in wallet, don't keep it in memory after window is closed
954 info.keepPassword = false;
955 }
956 }
957#endif
958 addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false);
959 }
960 info.setModified(true);
961 } else {
962 if (!bypassCacheAndKWallet && request->prompt) {
963 addAuthInfoItem(request->key, info, 0, m_seqNr, true);
964 }
965 info.setModified(false);
966 }
967
968 sendResponse(request.get());
969 }
970}
971
972void KPasswdServer::retryDialogDone(int result, KMessageDialog *sender)
973{
974 std::unique_ptr<Request> request(m_authRetryInProgress.take(sender));
975 Q_ASSERT(request);
976
977 if (request) {
978 if (result == KMessageDialog::PrimaryAction) {
979 showPasswordDialog(request.release());
980 } else {
981 // NOTE: If the user simply cancels the retry dialog, we remove the
982 // credential stored under this key because the original attempt to
983 // use it has failed. Otherwise, the failed credential would be cached
984 // and used subsequently.
985 //
986 // TODO: decide whether it should be removed from the wallet too.
987 KIO::AuthInfo &info = request->info;
988 removeAuthInfoItem(request->key, request->info);
989 info.setModified(false);
990 sendResponse(request.get());
991 }
992 }
993}
994
995void KPasswdServer::windowRemoved(WId id)
996{
997 bool foundMatch = false;
998 if (!m_authInProgress.isEmpty()) {
999 const qlonglong windowId = static_cast<qlonglong>(id);
1000 QMutableHashIterator<QObject *, Request *> it(m_authInProgress);
1001 while (it.hasNext()) {
1002 it.next();
1003 if (it.value()->windowId == windowId) {
1004 Request *request = it.value();
1005 QObject *obj = it.key();
1006 it.remove();
1007 m_authPrompted.removeAll(QString::number(request->windowId));
1008 m_authPrompted.removeAll(request->key);
1009 delete obj;
1010 delete request;
1011 foundMatch = true;
1012 }
1013 }
1014 }
1015
1016 if (!foundMatch && !m_authRetryInProgress.isEmpty()) {
1017 const qlonglong windowId = static_cast<qlonglong>(id);
1018 QMutableHashIterator<QObject *, Request *> it(m_authRetryInProgress);
1019 while (it.hasNext()) {
1020 it.next();
1021 if (it.value()->windowId == windowId) {
1022 Request *request = it.value();
1023 QObject *obj = it.key();
1024 it.remove();
1025 delete obj;
1026 delete request;
1027 }
1028 }
1029 }
1030}
1031
1032void KPasswdServer::updateCachedRequestKey(QList<KPasswdServer::Request *> &list, const QString &oldKey, const QString &newKey)
1033{
1034 QListIterator<Request *> it(list);
1035 while (it.hasNext()) {
1036 Request *r = it.next();
1037 if (r->key == oldKey) {
1038 r->key = newKey;
1039 }
1040 }
1041}
1042
1043#include "moc_kpasswdserver.cpp"
void windowUnregistered(qlonglong windowId)
This class is intended to make it easier to prompt for, cache and retrieve authorization information.
QString realmValue
A unique identifier that allows caching of multiple passwords for different resources in the same ser...
Definition authinfo.h:179
void setModified(bool flag)
Use this method to indicate that this object has been modified.
Definition authinfo.cpp:147
bool verifyPath
Flag that, if set, indicates whether a path match should be performed when requesting for cached auth...
Definition authinfo.h:202
QUrl url
The URL for which authentication is to be stored.
Definition authinfo.h:100
bool keepPassword
Flag to indicate the persistence of the given password.
Definition authinfo.h:222
QVariant getExtraField(const QString &fieldName) const
Get Extra Field Value Check QVariant::isValid() to find out if the field exists.
Definition authinfo.cpp:164
QString comment
Additional comment to be displayed when prompting the user for authentication information.
Definition authinfo.h:156
bool readOnly
Flag which if set forces the username field to be read-only.
Definition authinfo.h:209
QString caption
The text to displayed in the title bar of the password prompting dialog.
Definition authinfo.h:132
AuthInfo::FieldFlags getExtraFieldFlags(const QString &fieldName) const
Get Extra Field Flags.
Definition authinfo.cpp:173
void setExtraField(const QString &fieldName, const QVariant &value)
Set Extra Field Value.
Definition authinfo.cpp:154
QString username
This is required for caching.
Definition authinfo.h:105
static void registerMetaTypes()
Register the meta-types for AuthInfo.
Definition authinfo.cpp:182
QString password
This is required for caching.
Definition authinfo.h:110
QString prompt
Information to be displayed when prompting the user for authentication information.
Definition authinfo.h:121
QString commentLabel
Descriptive label to be displayed in front of the comment when prompting the user for password.
Definition authinfo.h:165
void setButtons(const KGuiItem &primaryAction=KGuiItem(), const KGuiItem &secondaryAction=KGuiItem(), const KGuiItem &cancelAction=KGuiItem())
void setDomain(const QString &)
void setKeepPassword(bool b)
void setAnonymousMode(bool anonymous)
void setUsername(const QString &)
void setPrompt(const QString &prompt)
void setPassword(const QString &password)
void addCommentLine(const QString &label, const QString &comment)
void setUsernameReadOnly(bool readOnly)
void setKnownLogins(const QMap< QString, QString > &knownLogins)
void setUsernameContextHelp(const QString &help)
static const QString PasswordFolder()
virtual bool createFolder(const QString &f)
virtual bool isOpen() const
static const QString NetworkWallet()
virtual bool hasFolder(const QString &f)
virtual int readMap(const QString &key, QMap< QString, QString > &value)
static bool isEnabled()
static Wallet * openWallet(const QString &name, WId w, OpenType ot=Synchronous)
virtual bool setFolder(const QString &f)
static bool keyDoesNotExist(const QString &wallet, const QString &folder, const QString &key)
virtual int writeMap(const QString &key, const QMap< QString, QString > &value)
static void setMainWindow(QWindow *subwindow, const QString &mainwindow)
void windowRemoved(WId id)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
QString i18n(const char *text, const TYPE &arg...)
const QList< QKeySequence > & end()
bool send(const QDBusMessage &message) const const
QDBusConnection sessionBus()
bool calledFromDBus() const const
const QDBusMessage & message() const const
void setDelayedReply(bool enable) const const
QDBusMessage createReply(const QList< QVariant > &arguments) const const
void finished(int result)
virtual void open()
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
T take(const Key &key)
T value(const Key &key) const const
QIcon fromTheme(const QString &name)
void append(QList< T > &&value)
qsizetype count() const const
bool isEmpty() const const
void prepend(parameter_type value)
qsizetype removeAll(const AT &t)
value_type takeFirst()
iterator begin()
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QObject * sender() const const
void setObjectName(QAnyStringView name)
iterator begin()
QString fromLatin1(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString left(qsizetype n) const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
WA_DeleteOnClose
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QString host(ComponentFormattingOptions options) const const
bool isValid() const const
QString path(ComponentFormattingOptions options) const const
int port(int defaultPort) const const
QString scheme() const const
void setUserName(const QString &userName, ParsingMode mode)
QString userName(ComponentFormattingOptions options) const const
bool isValid() const const
bool toBool() const const
QString toString() const const
QWidget * find(WId id)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
QWindow * windowHandle() const const
void setWindowIcon(const QIcon &icon)
void setWindowTitle(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:28 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.