diff --git a/src/kpasswdserver/autotests/kpasswdservertest.cpp b/src/kpasswdserver/autotests/kpasswdservertest.cpp index 167d5408..b04e6729 100644 --- a/src/kpasswdserver/autotests/kpasswdservertest.cpp +++ b/src/kpasswdserver/autotests/kpasswdservertest.cpp @@ -1,545 +1,549 @@ /* This file is part of the KDE project Copyright 2010 David Faure Copyright 2012 Dawit Alemayehu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or ( at your option ) version 3 or, at the discretion of KDE e.V. ( which shall act as a proxy as in section 14 of the GPLv3 ), any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Boston, MA 02110-1301, USA. */ #include #include #include #include #include static const char *sigQueryAuthInfoResult = SIGNAL(queryAuthInfoAsyncResult(qlonglong, qlonglong, KIO::AuthInfo)); static const char *sigCheckAuthInfoResult = SIGNAL(checkAuthInfoAsyncResult(qlonglong, qlonglong, KIO::AuthInfo)); // For the retry dialog (and only that one) static QDialogButtonBox::StandardButton s_buttonYes = QDialogButtonBox::Yes; static QDialogButtonBox::StandardButton s_buttonCancel = QDialogButtonBox::Cancel; Q_DECLARE_METATYPE(QDialogButtonBox::StandardButton) Q_DECLARE_METATYPE(QDialog::DialogCode) static QString getUserNameFrom(const KIO::AuthInfo &auth) { if (auth.username.isEmpty() && !auth.url.userName().isEmpty()) { return auth.url.userName(); } return auth.username; } class KPasswdServerTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qRegisterMetaType(); qRegisterMetaType(); } void simpleTest() { KPasswdServer server(this); server.setWalletDisabled(true); // Check that processRequest doesn't crash when it has nothing to do server.processRequest(); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // Make a check for that host, should say "not found" QVERIFY(noCheckAuth(server, info)); // Now add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo realInfo = info; realInfo.username = QStringLiteral("toto"); // you can see I'm french realInfo.password = QStringLiteral("foobar"); server.addAuthInfo(realInfo, windowId); // seqnr=2 // queryAuth without the ability to prompt, will just return info unmodified KIO::AuthInfo resultInfo; queryAuth(server, info, resultInfo); QCOMPARE(resultInfo.url, info.url); QCOMPARE(resultInfo.username, QString()); QCOMPARE(resultInfo.password, QString()); QCOMPARE(resultInfo.isModified(), false); // Check that checkAuth finds it QVERIFY(successCheckAuth(server, info, realInfo)); // Now remove auth server.removeAuthInfo(info.url.host(), info.url.scheme(), info.username); // Check we can't find that auth anymore QVERIFY(noCheckAuth(server, info)); } void testCheckDuringQuery() { KPasswdServer server(this); server.setWalletDisabled(true); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.kde.org")); // Start a query QSignalSpy spyQuery(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync(info, QStringLiteral(""), // magic string to avoid a dialog windowId, seqNr, 16 /*usertime*/); // Before it is processed, do a check, it will reply delayed. QSignalSpy spyCheck(&server, sigCheckAuthInfoResult); const qlonglong idCheck = server.checkAuthInfoAsync(info, windowId, 17 /*usertime*/); QCOMPARE(idCheck, 0LL); // always QCOMPARE(spyCheck.count(), 0); // no reply yet // Wait for the query to be processed QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spyQuery.count(), 1); QCOMPARE(spyQuery[0][0].toLongLong(), id); KIO::AuthInfo result = spyQuery[0][2].value(); // Now the check will have replied QCOMPARE(spyCheck.count(), 1); QCOMPARE(spyCheck[0][0].toLongLong(), id + 1); // it was the next request after the query KIO::AuthInfo resultCheck = spyCheck[0][2].value(); QCOMPARE(result.username, resultCheck.username); QCOMPARE(result.password, resultCheck.password); } void testExpiry() { KPasswdServer server(this); server.setWalletDisabled(true); KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // Add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo realInfo = info; realInfo.username = QStringLiteral("toto"); realInfo.password = QStringLiteral("foobar"); server.addAuthInfo(realInfo, windowId); QVERIFY(successCheckAuth(server, info, realInfo)); // Close another window, shouldn't hurt server.removeAuthForWindowId(windowId + 1); QVERIFY(successCheckAuth(server, info, realInfo)); // Close window server.removeAuthForWindowId(windowId); // Check we can't find that auth anymore QVERIFY(noCheckAuth(server, info)); } void testFillDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("dfaure"); filledInfo.password = QStringLiteral("toto"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); } void testRejectRetryDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("username"); filledInfo.password = QStringLiteral("password"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Pretend that the returned credentials failed and initiate a retry, // but cancel the retry dialog. info.password.clear(); result = KIO::AuthInfo(); - queryAuthWithDialog(server, info, filledInfo, result, s_buttonCancel, QDialog::Accepted /*unused*/, QStringLiteral("Invalid username or password")); + queryAuthWithDialog(server, info, filledInfo, result, s_buttonCancel, + QDialog::Accepted /*unused*/, QStringLiteral("Invalid username or password")); } void testAcceptRetryDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("username"); filledInfo.password = QStringLiteral("password"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Pretend that the returned credentials failed and initiate a retry, // but this time continue the retry. info.password.clear(); result = KIO::AuthInfo(); - queryAuthWithDialog(server, info, filledInfo, result, s_buttonYes, QDialog::Accepted, QStringLiteral("Invalid username or password")); + queryAuthWithDialog(server, info, filledInfo, result, s_buttonYes, + QDialog::Accepted, QStringLiteral("Invalid username or password")); } void testUsernameMistmatch() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask. Note the username in the URL. KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://foo@www.example.com")); // What the user would type KIO::AuthInfo filledInfo(info); filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); KIO::AuthInfo result; queryAuthWithDialog(server, info, filledInfo, result); // Check the returned url does not match the request url because of the // username mismatch between the request URL and the filled in one. QVERIFY(result.url != filledInfo.url); // Verify there is NO cached auth data if the request URL contains the // original user name (foo). QVERIFY(noCheckAuth(server, info)); // Verify there is a cached auth data if the request URL contains the // new user name (bar). filledInfo.url = QUrl(QStringLiteral("http://bar@www.example.com")); QVERIFY(successCheckAuth(server, filledInfo, result)); // Now the URL check should be valid too. QCOMPARE(result.url, filledInfo.url); } void testCancelPasswordDialog() { KPasswdServer server(this); server.setWalletDisabled(true); // What the app would ask. KIO::AuthInfo info; info.url = QUrl(QStringLiteral("http://www.example.com")); info.username = info.url.userName(); KIO::AuthInfo result; queryAuthWithDialog(server, info, KIO::AuthInfo(), result, QDialogButtonBox::NoButton, QDialog::Rejected); } void testVerifyPath() { KPasswdServer server(this); server.setWalletDisabled(true); // Add auth to the cache const qlonglong windowId = 42; KIO::AuthInfo authInfo; authInfo.url = QUrl(QStringLiteral("http://www.example.com/test/test.html")); authInfo.username = QStringLiteral("toto"); authInfo.password = QStringLiteral("foobar"); server.addAuthInfo(authInfo, windowId); KIO::AuthInfo queryAuthInfo; queryAuthInfo.url = QUrl(QStringLiteral("http://www.example.com/test/test2/test.html")); queryAuthInfo.verifyPath = true; KIO::AuthInfo expectedAuthInfo; expectedAuthInfo.username = QStringLiteral("toto"); expectedAuthInfo.password = QStringLiteral("foobar"); QVERIFY(successCheckAuth(server, queryAuthInfo, expectedAuthInfo)); } void testConcurrentQueryAuth() { KPasswdServer server(this); server.setWalletDisabled(true); QList authInfos; for (int i = 0; i < 10; ++i) { KIO::AuthInfo info; info.url = QUrl(QLatin1String("http://www.example.com/test") + QString::number(i) + QLatin1String(".html")); authInfos << info; } // What the user would type KIO::AuthInfo filledInfo; filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); QList results; concurrentQueryAuthWithDialog(server, authInfos, filledInfo, results); } void testConcurrentCheckAuth() { KPasswdServer server(this); server.setWalletDisabled(true); QList authInfos; for (int i = 0; i < 10; ++i) { KIO::AuthInfo info; info.url = QUrl(QLatin1String("http://www.example.com/test") + QString::number(i) + QStringLiteral(".html")); authInfos << info; } // What the user would type KIO::AuthInfo filledInfo; filledInfo.username = QStringLiteral("bar"); filledInfo.password = QStringLiteral("blah"); QList results; concurrentQueryAuthWithDialog(server, authInfos, filledInfo, results); } private: // Checks that no auth is available for @p info bool noCheckAuth(KPasswdServer &server, const KIO::AuthInfo &info) { KIO::AuthInfo result; checkAuth(server, info, result); return (result.username == info.username) && (result.password == info.password) && !result.isModified(); } // Check that the auth is available and equal to @expectedInfo bool successCheckAuth(KPasswdServer &server, const KIO::AuthInfo &info, const KIO::AuthInfo &expectedInfo) { KIO::AuthInfo result; checkAuth(server, info, result); return (result.username == expectedInfo.username) && (result.password == expectedInfo.password) && result.isModified(); } void checkAuth(KPasswdServer &server, const KIO::AuthInfo &info, KIO::AuthInfo &result) { QSignalSpy spy(&server, sigCheckAuthInfoResult); const qlonglong windowId = 42; const qlonglong id = server.checkAuthInfoAsync(info, windowId, 17 /*usertime*/); QCOMPARE(id, 0LL); // always if (spy.isEmpty()) { QVERIFY(QSignalSpy(&server, sigCheckAuthInfoResult).wait(1000)); } QCOMPARE(spy.count(), 1); // kpasswdserver emits a requestId via dbus, we can't get that id here QVERIFY(spy[0][0].toLongLong() >= 0); // QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); } void queryAuth(KPasswdServer &server, const KIO::AuthInfo &info, KIO::AuthInfo &result) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync(info, QStringLiteral(""), // magic string to avoid a dialog windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing if (spy.isEmpty()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), 1); QCOMPARE(spy[0][0].toLongLong(), id); // QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); } void queryAuthWithDialog(KPasswdServer &server, const KIO::AuthInfo &info, const KIO::AuthInfo &filledInfo, KIO::AuthInfo &result, QDialogButtonBox::StandardButton retryButton = s_buttonYes, QDialog::DialogCode code = QDialog::Accepted, const QString &errMsg = QString()) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; const qlonglong seqNr = 2; const qlonglong id = server.queryAuthInfoAsync(info, errMsg, windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing QVERIFY(spy.isEmpty()); const bool hasErrorMessage = (!errMsg.isEmpty()); const bool isCancelRetryDialogTest = (hasErrorMessage && retryButton == s_buttonCancel); if (hasErrorMessage) { // Retry dialog only knows Yes/No - QMetaObject::invokeMethod(this, "checkRetryDialog", Qt::QueuedConnection, Q_ARG(QDialogButtonBox::StandardButton, retryButton)); + QMetaObject::invokeMethod(this, "checkRetryDialog", Qt::QueuedConnection, + Q_ARG(QDialogButtonBox::StandardButton, retryButton)); } if (!isCancelRetryDialogTest) { - QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, info), Q_ARG(KIO::AuthInfo, filledInfo), Q_ARG(QDialog::DialogCode, code)); + QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, info), + Q_ARG(KIO::AuthInfo, filledInfo), Q_ARG(QDialog::DialogCode, code)); } // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); if (spy.isEmpty()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), 1); QCOMPARE(spy[0][0].toLongLong(), id); // QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr result = spy[0][2].value(); const QString username = (isCancelRetryDialogTest ? QString() : filledInfo.username); const QString password = (isCancelRetryDialogTest ? QString() : filledInfo.password); QCOMPARE(result.username, username); QCOMPARE(result.password, password); QCOMPARE(result.isModified(), retryButton == s_buttonYes && code == QDialog::Accepted); } void concurrentQueryAuthWithDialog(KPasswdServer &server, const QList &infos, const KIO::AuthInfo &filledInfo, QList &results, QDialog::DialogCode code = QDialog::Accepted) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; qlonglong seqNr = 0; QList idList; for (const KIO::AuthInfo &info : infos) { const qlonglong id = server.queryAuthInfoAsync(info, QString(), windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } QVERIFY(spy.isEmpty()); QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, infos.first()), Q_ARG(KIO::AuthInfo, filledInfo), Q_ARG(QDialog::DialogCode, code)); // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); while (spy.count() < infos.count()) QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); QCOMPARE(spy.count(), infos.count()); for (int i = 0, count = spy.count(); i < count; ++i) { QCOMPARE(spy[i][0].toLongLong(), idList.at(i)); // QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr KIO::AuthInfo result = spy[i][2].value(); QCOMPARE(result.username, filledInfo.username); QCOMPARE(result.password, filledInfo.password); QCOMPARE(result.isModified(), code == QDialog::Accepted); results << result; } } void concurrentCheckAuthWithDialog(KPasswdServer &server, const QList &infos, const KIO::AuthInfo &filledInfo, QList &results, QDialog::DialogCode code = QDialog::Accepted) { QSignalSpy spy(&server, sigQueryAuthInfoResult); const qlonglong windowId = 42; qlonglong seqNr = 0; QList idList; QListIterator it(infos); if (it.hasNext()) { const qlonglong id = server.queryAuthInfoAsync(it.next(), QString(), windowId, seqNr, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } while (it.hasNext()) { const qlonglong id = server.checkAuthInfoAsync(it.next(), windowId, 16 /*usertime*/); QVERIFY(id >= 0); // requestId, ever increasing idList << id; } QVERIFY(spy.isEmpty()); QMetaObject::invokeMethod(this, "checkAndFillDialog", Qt::QueuedConnection, Q_ARG(KIO::AuthInfo, infos.first()), Q_ARG(KIO::AuthInfo, filledInfo), Q_ARG(QDialog::DialogCode, code)); // Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too... server.processRequest(); if (spy.isEmpty()) { QVERIFY(QSignalSpy(&server, sigQueryAuthInfoResult).wait(1000)); } while ((spy.count() - 1) < infos.count()) { QVERIFY(QSignalSpy(&server, sigCheckAuthInfoResult).wait(1000)); } for (int i = 0, count = spy.count(); i < count; ++i) { QCOMPARE(spy[i][0].toLongLong(), idList.at(i)); // QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr KIO::AuthInfo result = spy[i][2].value(); QCOMPARE(result.username, filledInfo.username); QCOMPARE(result.password, filledInfo.password); QCOMPARE(result.isModified(), code == QDialog::Accepted); results << result; } } protected Q_SLOTS: void checkAndFillDialog(const KIO::AuthInfo &info, const KIO::AuthInfo &filledInfo, QDialog::DialogCode code) { const QList widgetsList = QApplication::topLevelWidgets(); for (QWidget *widget : widgetsList) { if (KPasswordDialog *dialog = qobject_cast(widget)) { if (code == QDialog::Accepted) { QCOMPARE(dialog->username(), getUserNameFrom(info)); QCOMPARE(dialog->password(), info.password); dialog->setUsername(filledInfo.username); dialog->setPassword(filledInfo.password); } dialog->done(code); return; } } qWarning() << "No KPasswordDialog found!"; } void checkRetryDialog(QDialogButtonBox::StandardButton code = s_buttonYes) { const QList widgetsList = QApplication::topLevelWidgets(); for (QWidget *widget : widgetsList) { QDialog *dialog = qobject_cast(widget); if (dialog && !dialog->inherits("KPasswordDialog")) { dialog->done(code); return; } } qWarning() << "No retry dialog found"; } }; QTEST_MAIN(KPasswdServerTest) #include "kpasswdservertest.moc" diff --git a/src/kpasswdserver/kpasswdserver.cpp b/src/kpasswdserver/kpasswdserver.cpp index c89b14c6..917f11ec 100644 --- a/src/kpasswdserver/kpasswdserver.cpp +++ b/src/kpasswdserver/kpasswdserver.cpp @@ -1,1003 +1,1064 @@ /* This file is part of the KDE Password Server Copyright (C) 2002 Waldo Bastian (bastian@kde.org) Copyright (C) 2005 David Faure (faure@kde.org) Copyright (C) 2012 Dawit Alemayehu (adawit@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //---------------------------------------------------------------------------- // // KDE Password Server #include "kpasswdserver.h" #include "kpasswdserveradaptor.h" #include #include #include #include #include #ifdef HAVE_KF5WALLET #include #endif #include #include #include static QLoggingCategory category("org.kde.kio.kpasswdserver", QtInfoMsg); #define AUTHINFO_EXTRAFIELD_DOMAIN QStringLiteral("domain") #define AUTHINFO_EXTRAFIELD_ANONYMOUS QStringLiteral("anonymous") #define AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET QStringLiteral("bypass-cache-and-kwallet") #define AUTHINFO_EXTRAFIELD_SKIP_CACHING_ON_QUERY QStringLiteral("skip-caching-on-query") #define AUTHINFO_EXTRAFIELD_HIDE_USERNAME_INPUT QStringLiteral("hide-username-line") static qlonglong getRequestId() { static qlonglong nextRequestId = 0; return nextRequestId++; } bool KPasswdServer::AuthInfoContainer::Sorter::operator()(AuthInfoContainer *n1, AuthInfoContainer *n2) const { - if (!n1 || !n2) + if (!n1 || !n2) { return 0; + } const int l1 = n1->directory.length(); const int l2 = n2->directory.length(); return l1 < l2; } KPasswdServer::KPasswdServer(QObject *parent, const QList &) : KDEDModule(parent) { KIO::AuthInfo::registerMetaTypes(); m_seqNr = 0; m_wallet = nullptr; m_walletDisabled = false; KPasswdServerAdaptor *adaptor = new KPasswdServerAdaptor(this); // connect signals to the adaptor connect(this, &KPasswdServer::checkAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::checkAuthInfoAsyncResult); connect(this, &KPasswdServer::queryAuthInfoAsyncResult, adaptor, &KPasswdServerAdaptor::queryAuthInfoAsyncResult); connect(this, &KDEDModule::windowUnregistered, this, &KPasswdServer::removeAuthForWindowId); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &KPasswdServer::windowRemoved); } KPasswdServer::~KPasswdServer() { // TODO: what about clients waiting for requests? will they just // notice kpasswdserver is gone from the dbus? qDeleteAll(m_authPending); qDeleteAll(m_authWait); qDeleteAll(m_authDict); qDeleteAll(m_authInProgress); qDeleteAll(m_authRetryInProgress); #ifdef HAVE_KF5WALLET delete m_wallet; #endif } #ifdef HAVE_KF5WALLET // Helper - returns the wallet key to use for read/store/checking for existence. static QString makeWalletKey(const QString &key, const QString &realm) { return realm.isEmpty() ? key : key + QLatin1Char('-') + realm; } // Helper for storeInWallet/readFromWallet static QString makeMapKey(const char *key, int entryNumber) { QString str = QLatin1String(key); - if (entryNumber > 1) + if (entryNumber > 1) { str += QLatin1Char('-') + QString::number(entryNumber); + } return str; } static bool storeInWallet(KWallet::Wallet *wallet, const QString &key, const KIO::AuthInfo &info) { - if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder())) - if (!wallet->createFolder(KWallet::Wallet::PasswordFolder())) + if (!wallet->hasFolder(KWallet::Wallet::PasswordFolder())) { + if (!wallet->createFolder(KWallet::Wallet::PasswordFolder())) { return false; + } + } wallet->setFolder(KWallet::Wallet::PasswordFolder()); // Before saving, check if there's already an entry with this login. // If so, replace it (with the new password). Otherwise, add a new entry. typedef QMap Map; int entryNumber = 1; Map map; QString walletKey = makeWalletKey(key, info.realmValue); qCDebug(category) << "walletKey =" << walletKey << " reading existing map"; if (wallet->readMap(walletKey, map) == 0) { Map::ConstIterator end = map.constEnd(); Map::ConstIterator it = map.constFind(QStringLiteral("login")); while (it != end) { if (it.value() == info.username) { break; // OK, overwrite this entry } it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber)); } // If no entry was found, create a new entry - entryNumber is set already. } const QString loginKey = makeMapKey("login", entryNumber); const QString passwordKey = makeMapKey("password", entryNumber); qCDebug(category) << "writing to " << loginKey << "," << passwordKey; // note the overwrite=true by default map.insert(loginKey, info.username); map.insert(passwordKey, info.password); wallet->writeMap(walletKey, map); return true; } -static bool readFromWallet(KWallet::Wallet *wallet, const QString &key, const QString &realm, QString &username, QString &password, bool userReadOnly, QMap &knownLogins) +static bool readFromWallet(KWallet::Wallet *wallet, const QString &key, const QString &realm, QString &username, + QString &password, bool userReadOnly, QMap &knownLogins) { - // qCDebug(category) << "key =" << key << " username =" << username << " password =" /*<< password*/ << " userReadOnly =" << userReadOnly << " realm =" << realm; + // qCDebug(category) << "key =" << key << " username =" << username << " password =" /*<< password*/ + // << " userReadOnly =" << userReadOnly << " realm =" << realm; if (wallet->hasFolder(KWallet::Wallet::PasswordFolder())) { wallet->setFolder(KWallet::Wallet::PasswordFolder()); QMap map; if (wallet->readMap(makeWalletKey(key, realm), map) == 0) { typedef QMap Map; int entryNumber = 1; Map::ConstIterator end = map.constEnd(); Map::ConstIterator it = map.constFind(QStringLiteral("login")); while (it != end) { // qCDebug(category) << "found " << it.key() << "=" << it.value(); Map::ConstIterator pwdIter = map.constFind(makeMapKey("password", entryNumber)); if (pwdIter != end) { - if (it.value() == username) + if (it.value() == username) { password = pwdIter.value(); + } knownLogins.insert(it.value(), pwdIter.value()); } it = map.constFind(QStringLiteral("login-") + QString::number(++entryNumber)); } // qCDebug(category) << knownLogins.count() << " known logins"; if (!userReadOnly && !knownLogins.isEmpty() && username.isEmpty()) { // Pick one, any one... username = knownLogins.begin().key(); password = knownLogins.begin().value(); // qCDebug(category) << "picked the first one:" << username; } return true; } } return false; } #endif bool KPasswdServer::hasPendingQuery(const QString &key, const KIO::AuthInfo &info) { const QString path2(info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1)); for (const Request *request : qAsConst(m_authPending)) { if (request->key != key) { continue; } if (info.verifyPath) { const QString path1(request->info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1)); if (!path2.startsWith(path1)) { continue; } } return true; } return false; } // deprecated method, not used anymore. TODO KF6: REMOVE QByteArray KPasswdServer::checkAuthInfo(const QByteArray &data, qlonglong windowId, qlonglong usertime) { KIO::AuthInfo info; QDataStream stream(data); stream >> info; if (usertime != 0) { KUserTimestamp::updateUserTimestamp(usertime); } // if the check depends on a pending query, delay it // until that query is finished. const QString key(createCacheKey(info)); if (hasPendingQuery(key, info)) { setDelayedReply(true); Request *pendingCheck = new Request; pendingCheck->isAsync = false; if (calledFromDBus()) { pendingCheck->transaction = message(); } pendingCheck->key = key; pendingCheck->info = info; m_authWait.append(pendingCheck); return data; // return value will be ignored } // qCDebug(category) << "key =" << key << "user =" << info.username << "windowId =" << windowId; const AuthInfoContainer *result = findAuthInfoItem(key, info); if (!result || result->isCanceled) { #ifdef HAVE_KF5WALLET - if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) && - !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { + if (!result + && !m_walletDisabled + && (info.username.isEmpty() || info.password.isEmpty()) + && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), + makeWalletKey(key, info.realmValue))) { QMap knownLogins; if (openWallet(windowId)) { - if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { + if (readFromWallet(m_wallet, key, info.realmValue, info.username, + info.password, info.readOnly, knownLogins)) { info.setModified(true); // fall through } } } else { info.setModified(false); } #else info.setModified(false); #endif } else { qCDebug(category) << "Found cached authentication for" << key; updateAuthExpire(key, result, windowId, false); copyAuthInfo(result, info); } QByteArray data2; QDataStream stream2(&data2, QIODevice::WriteOnly); stream2 << info; return data2; } qlonglong KPasswdServer::checkAuthInfoAsync(KIO::AuthInfo info, qlonglong windowId, qlonglong usertime) { if (usertime != 0) { KUserTimestamp::updateUserTimestamp(usertime); } // send the request id back to the client qlonglong requestId = getRequestId(); qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId; if (calledFromDBus()) { QDBusMessage reply(message().createReply(requestId)); QDBusConnection::sessionBus().send(reply); } // if the check depends on a pending query, delay it // until that query is finished. const QString key(createCacheKey(info)); if (hasPendingQuery(key, info)) { Request *pendingCheck = new Request; pendingCheck->isAsync = true; pendingCheck->requestId = requestId; pendingCheck->key = key; pendingCheck->info = info; m_authWait.append(pendingCheck); return 0; // ignored as we already sent a reply } const AuthInfoContainer *result = findAuthInfoItem(key, info); if (!result || result->isCanceled) { #ifdef HAVE_KF5WALLET - if (!result && !m_walletDisabled && (info.username.isEmpty() || info.password.isEmpty()) && - !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(key, info.realmValue))) { + if (!result + && !m_walletDisabled + && (info.username.isEmpty() || info.password.isEmpty()) + && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), + makeWalletKey(key, info.realmValue))) { QMap knownLogins; if (openWallet(windowId)) { - if (readFromWallet(m_wallet, key, info.realmValue, info.username, info.password, info.readOnly, knownLogins)) { + if (readFromWallet(m_wallet, key, info.realmValue, info.username, + info.password, info.readOnly, knownLogins)) { info.setModified(true); // fall through } } } else { info.setModified(false); } #else info.setModified(false); #endif } else { // qCDebug(category) << "Found cached authentication for" << key; updateAuthExpire(key, result, windowId, false); copyAuthInfo(result, info); } emit checkAuthInfoAsyncResult(requestId, m_seqNr, info); return 0; // ignored } // deprecated method, not used anymore. TODO KF6: REMOVE -QByteArray KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) +QByteArray KPasswdServer::queryAuthInfo(const QByteArray &data, const QString &errorMsg, + qlonglong windowId, qlonglong seqNr, qlonglong usertime) { KIO::AuthInfo info; QDataStream stream(data); stream >> info; - qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; + qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId + << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; if (!info.password.isEmpty()) { // should we really allow the caller to pre-fill the password? qCDebug(category) << "password was set by caller"; } if (usertime != 0) { KUserTimestamp::updateUserTimestamp(usertime); } const QString key(createCacheKey(info)); Request *request = new Request; setDelayedReply(true); request->isAsync = false; request->transaction = message(); request->key = key; request->info = info; request->windowId = windowId; request->seqNr = seqNr; if (errorMsg == QLatin1String("")) { request->errorMsg.clear(); request->prompt = false; } else { request->errorMsg = errorMsg; request->prompt = true; } m_authPending.append(request); - if (m_authPending.count() == 1) + if (m_authPending.count() == 1) { QTimer::singleShot(0, this, &KPasswdServer::processRequest); + } return QByteArray(); // return value is going to be ignored } -qlonglong KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg, qlonglong windowId, qlonglong seqNr, qlonglong usertime) +qlonglong KPasswdServer::queryAuthInfoAsync(const KIO::AuthInfo &info, const QString &errorMsg, + qlonglong windowId,qlonglong seqNr, qlonglong usertime) { - qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; + qCDebug(category) << "User =" << info.username << ", WindowId =" << windowId + << "seqNr =" << seqNr << ", errorMsg =" << errorMsg; if (!info.password.isEmpty()) { qCDebug(category) << "password was set by caller"; } if (usertime != 0) { KUserTimestamp::updateUserTimestamp(usertime); } const QString key(createCacheKey(info)); Request *request = new Request; request->isAsync = true; request->requestId = getRequestId(); request->key = key; request->info = info; request->windowId = windowId; request->seqNr = seqNr; if (errorMsg == QLatin1String("")) { request->errorMsg.clear(); request->prompt = false; } else { request->errorMsg = errorMsg; request->prompt = true; } m_authPending.append(request); if (m_authPending.count() == 1) { QTimer::singleShot(0, this, &KPasswdServer::processRequest); } return request->requestId; } void KPasswdServer::addAuthInfo(const KIO::AuthInfo &info, qlonglong windowId) { - qCDebug(category) << "User =" << info.username << ", Realm =" << info.realmValue << ", WindowId =" << windowId; + qCDebug(category) << "User =" << info.username << ", Realm =" << info.realmValue + << ", WindowId =" << windowId; const QString key(createCacheKey(info)); m_seqNr++; #ifdef HAVE_KF5WALLET if (!m_walletDisabled && openWallet(windowId) && storeInWallet(m_wallet, key, info)) { // Since storing the password in the wallet succeeded, make sure the // password information is stored in memory only for the duration the // windows associated with it are still around. KIO::AuthInfo authToken(info); authToken.keepPassword = false; addAuthInfoItem(key, authToken, windowId, m_seqNr, false); return; } #endif addAuthInfoItem(key, info, windowId, m_seqNr, false); } // deprecated method, not used anymore. TODO KF6: REMOVE void KPasswdServer::addAuthInfo(const QByteArray &data, qlonglong windowId) { KIO::AuthInfo info; QDataStream stream(data); stream >> info; addAuthInfo(info, windowId); } void KPasswdServer::removeAuthInfo(const QString &host, const QString &protocol, const QString &user) { qCDebug(category) << protocol << host << user; QHashIterator dictIterator(m_authDict); while (dictIterator.hasNext()) { dictIterator.next(); const AuthInfoContainerList *authList = dictIterator.value(); - if (!authList) + if (!authList) { continue; + } for (const AuthInfoContainer *current : *authList) { - qCDebug(category) << "Evaluating: " << current->info.url.scheme() << current->info.url.host() << current->info.username; - if (current->info.url.scheme() == protocol && current->info.url.host() == host && (current->info.username == user || user.isEmpty())) { + qCDebug(category) << "Evaluating: " << current->info.url.scheme() + << current->info.url.host() << current->info.username; + if (current->info.url.scheme() == protocol + && current->info.url.host() == host + && (current->info.username == user || user.isEmpty())) { qCDebug(category) << "Removing this entry"; removeAuthInfoItem(dictIterator.key(), current->info); } } } } #ifdef HAVE_KF5WALLET bool KPasswdServer::openWallet(qlonglong windowId) { if (m_wallet && !m_wallet->isOpen()) { // forced closed delete m_wallet; m_wallet = nullptr; } - if (!m_wallet) + if (!m_wallet) { m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(), (WId)(windowId)); + } return m_wallet != nullptr; } #endif void KPasswdServer::processRequest() { if (m_authPending.isEmpty()) { return; } QScopedPointer request(m_authPending.takeFirst()); // Prevent multiple prompts originating from the same window or the same // key (server address). const QString windowIdStr = QString::number(request->windowId); if (m_authPrompted.contains(windowIdStr) || m_authPrompted.contains(request->key)) { m_authPending.prepend(request.take()); // put it back. return; } m_authPrompted.append(windowIdStr); m_authPrompted.append(request->key); KIO::AuthInfo &info = request->info; // NOTE: If info.username is empty and info.url.userName() is not, set // info.username to info.url.userName() to ensure proper caching. See // note passwordDialogDone. if (info.username.isEmpty() && !info.url.userName().isEmpty()) { info.username = info.url.userName(); } const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool(); const AuthInfoContainer *result = findAuthInfoItem(request->key, request->info); - qCDebug(category) << "key=" << request->key << ", user=" << info.username << "seqNr: request=" << request->seqNr << ", result=" << (result ? result->seqNr : -1); + qCDebug(category) << "key=" << request->key << ", user=" << info.username + << "seqNr: request=" << request->seqNr + <<", result=" << (result ? result->seqNr : -1); if (!bypassCacheAndKWallet && result && (request->seqNr < result->seqNr)) { qCDebug(category) << "auto retry!"; if (result->isCanceled) { info.setModified(false); } else { updateAuthExpire(request->key, result, request->windowId, false); copyAuthInfo(result, info); } } else { m_seqNr++; if (result && !request->errorMsg.isEmpty()) { const QString prompt = request->errorMsg.trimmed() + QLatin1Char('\n') + i18n("Do you want to retry?"); QDialog *dlg = new QDialog; connect(dlg, &QDialog::finished, this, &KPasswdServer::retryDialogDone); connect(this, &QObject::destroyed, dlg, &QObject::deleteLater); dlg->setWindowTitle(i18n("Retry Authentication")); dlg->setWindowIcon(QIcon::fromTheme(QStringLiteral("dialog-password"))); dlg->setObjectName(QStringLiteral("warningOKCancel")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::Cancel); buttonBox->button(QDialogButtonBox::Yes)->setText(i18nc("@action:button filter-continue", "Retry")); - KMessageBox::createKMessageBox(dlg, buttonBox, QMessageBox::Warning, prompt, QStringList(), QString(), nullptr, (KMessageBox::Notify | KMessageBox::NoExec)); + KMessageBox::createKMessageBox(dlg, buttonBox, QMessageBox::Warning, prompt, QStringList(), + QString(), nullptr, (KMessageBox::Notify | KMessageBox::NoExec)); dlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); qCDebug(category) << "Calling open on retry dialog" << dlg; m_authRetryInProgress.insert(dlg, request.take()); dlg->open(); return; } if (request->prompt) { showPasswordDialog(request.take()); return; } else { if (!bypassCacheAndKWallet && request->prompt) { addAuthInfoItem(request->key, info, 0, m_seqNr, true); } info.setModified(false); } } sendResponse(request.data()); } QString KPasswdServer::createCacheKey(const KIO::AuthInfo &info) { if (!info.url.isValid()) { // Note that a null key will break findAuthInfoItem later on... qCWarning(category) << "createCacheKey: invalid URL " << info.url; return QString(); } // Generate the basic key sequence. QString key = info.url.scheme(); key += QLatin1Char('-'); if (!info.url.userName().isEmpty()) { key += info.url.userName() + QLatin1Char('@'); } key += info.url.host(); int port = info.url.port(); if (port) { key += QLatin1Char(':') + QString::number(port); } return key; } void KPasswdServer::copyAuthInfo(const AuthInfoContainer *i, KIO::AuthInfo &info) { info = i->info; info.setModified(true); } const KPasswdServer::AuthInfoContainer *KPasswdServer::findAuthInfoItem(const QString &key, const KIO::AuthInfo &info) { // qCDebug(category) << "key=" << key << ", user=" << info.username; AuthInfoContainerList *authList = m_authDict.value(key); if (authList) { QString path2 = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); auto it = authList->begin(); while (it != authList->end()) { AuthInfoContainer *current = (*it); - if (current->expire == AuthInfoContainer::expTime && static_cast(time(nullptr)) > current->expireTime) { + if (current->expire == AuthInfoContainer::expTime + && static_cast(time(nullptr)) > current->expireTime) { delete current; it = authList->erase(it); continue; } if (info.verifyPath) { QString path1 = current->directory; - if (path2.startsWith(path1) && (info.username.isEmpty() || info.username == current->info.username)) + if (path2.startsWith(path1) + && (info.username.isEmpty() || info.username == current->info.username)) return current; } else { - if (current->info.realmValue == info.realmValue && (info.username.isEmpty() || info.username == current->info.username)) + if (current->info.realmValue == info.realmValue + && (info.username.isEmpty() || info.username == current->info.username)) return current; // TODO: Update directory info, } ++it; } } return nullptr; } void KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info) { AuthInfoContainerList *authList = m_authDict.value(key); if (!authList) return; auto it = authList->begin(); while (it != authList->end()) { if ((*it)->info.realmValue == info.realmValue) { delete (*it); it = authList->erase(it); } else { ++it; } } if (authList->isEmpty()) { delete m_authDict.take(key); } } -void KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled) +void KPasswdServer::addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, + qlonglong windowId, qlonglong seqNr, bool canceled) { - qCDebug(category) << "key=" << key << "window-id=" << windowId << "username=" << info.username << "realm=" << info.realmValue << "seqNr=" << seqNr << "keepPassword?" << info.keepPassword << "canceled?" << canceled; + qCDebug(category) << "key=" << key << "window-id=" << windowId << "username=" << info.username + << "realm=" << info.realmValue << "seqNr=" << seqNr + << "keepPassword?" << info.keepPassword << "canceled?" << canceled; AuthInfoContainerList *authList = m_authDict.value(key); if (!authList) { authList = new AuthInfoContainerList; m_authDict.insert(key, authList); } AuthInfoContainer *authItem = nullptr; auto it = authList->begin(); while (it != authList->end()) { if ((*it)->info.realmValue == info.realmValue) { authItem = (*it); it = authList->erase(it); break; } else { ++it; } } if (!authItem) { qCDebug(category) << "Creating AuthInfoContainer"; authItem = new AuthInfoContainer; authItem->expire = AuthInfoContainer::expTime; } authItem->info = info; authItem->directory = info.url.path().left(info.url.path().indexOf(QLatin1Char('/')) + 1); authItem->seqNr = seqNr; authItem->isCanceled = canceled; updateAuthExpire(key, authItem, windowId, (info.keepPassword && !canceled)); // Insert into list, keep the list sorted "longest path" first. authList->append(authItem); std::sort(authList->begin(), authList->end(), AuthInfoContainer::Sorter()); } -void KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, qlonglong windowId, bool keep) +void KPasswdServer::updateAuthExpire(const QString &key, const AuthInfoContainer *auth, + qlonglong windowId, bool keep) { AuthInfoContainer *current = const_cast(auth); Q_ASSERT(current); - qCDebug(category) << "key=" << key << "expire=" << current->expire << "window-id=" << windowId << "keep=" << keep; + qCDebug(category) << "key=" << key << "expire=" << current->expire + << "window-id=" << windowId << "keep=" << keep; if (keep && !windowId) { current->expire = AuthInfoContainer::expNever; } else if (windowId && (current->expire != AuthInfoContainer::expNever)) { current->expire = AuthInfoContainer::expWindowClose; - if (!current->windowList.contains(windowId)) + if (!current->windowList.contains(windowId)) { current->windowList.append(windowId); + } } else if (current->expire == AuthInfoContainer::expTime) { current->expireTime = time(nullptr) + 10; } // Update mWindowIdList if (windowId) { QStringList &keysChanged = mWindowIdList[windowId]; // find or insert if (!keysChanged.contains(key)) keysChanged.append(key); } } void KPasswdServer::removeAuthForWindowId(qlonglong windowId) { const QStringList keysChanged = mWindowIdList.value(windowId); for (const QString &key : keysChanged) { AuthInfoContainerList *authList = m_authDict.value(key); - if (!authList) + if (!authList) { continue; + } QMutableListIterator it(*authList); while (it.hasNext()) { AuthInfoContainer *current = it.next(); if (current->expire == AuthInfoContainer::expWindowClose) { if (current->windowList.removeAll(windowId) && current->windowList.isEmpty()) { delete current; it.remove(); } } } } } void KPasswdServer::showPasswordDialog(KPasswdServer::Request *request) { KIO::AuthInfo &info = request->info; QString username = info.username; QString password = info.password; bool hasWalletData = false; QMap knownLogins; #ifdef HAVE_KF5WALLET const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool(); - if (!bypassCacheAndKWallet && (username.isEmpty() || password.isEmpty()) && !m_walletDisabled && - !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), makeWalletKey(request->key, info.realmValue))) { + if (!bypassCacheAndKWallet + && (username.isEmpty() || password.isEmpty()) + && !m_walletDisabled + && !KWallet::Wallet::keyDoesNotExist(KWallet::Wallet::NetworkWallet(), KWallet::Wallet::PasswordFolder(), + makeWalletKey(request->key, info.realmValue))) { // no login+pass provided, check if kwallet has one - if (openWallet(request->windowId)) - hasWalletData = readFromWallet(m_wallet, request->key, info.realmValue, username, password, info.readOnly, knownLogins); + if (openWallet(request->windowId)) { + hasWalletData = readFromWallet(m_wallet, request->key, info.realmValue, + username, password, info.readOnly, knownLogins); + } } #endif // assemble dialog-flags KPasswordDialog::KPasswordDialogFlags dialogFlags; if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid()) { dialogFlags |= KPasswordDialog::ShowDomainLine; if (info.getExtraFieldFlags(AUTHINFO_EXTRAFIELD_DOMAIN) & KIO::AuthInfo::ExtraFieldReadOnly) { dialogFlags |= KPasswordDialog::DomainReadOnly; } } if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid()) { dialogFlags |= KPasswordDialog::ShowAnonymousLoginCheckBox; } if (!info.getExtraField(AUTHINFO_EXTRAFIELD_HIDE_USERNAME_INPUT).toBool()) { dialogFlags |= KPasswordDialog::ShowUsernameLine; } #ifdef HAVE_KF5WALLET // If wallet is not enabled and the caller explicitly requested for it, // do not show the keep password checkbox. - if (info.keepPassword && KWallet::Wallet::isEnabled()) + if (info.keepPassword && KWallet::Wallet::isEnabled()) { dialogFlags |= KPasswordDialog::ShowKeepPassword; + } #endif // instantiate dialog qCDebug(category) << "Widget for" << request->windowId << QWidget::find(request->windowId); KPasswordDialog *dlg = new KPasswordDialog(nullptr, dialogFlags); connect(dlg, &QDialog::finished, this, &KPasswdServer::passwordDialogDone); connect(this, &QObject::destroyed, dlg, &QObject::deleteLater); dlg->setPrompt(info.prompt); dlg->setUsername(username); - if (info.caption.isEmpty()) + if (info.caption.isEmpty()) { dlg->setWindowTitle(i18n("Authentication Dialog")); - else + } else { dlg->setWindowTitle(info.caption); + } - if (!info.comment.isEmpty()) + if (!info.comment.isEmpty()) { dlg->addCommentLine(info.commentLabel, info.comment); + } - if (!password.isEmpty()) + if (!password.isEmpty()) { dlg->setPassword(password); + } - if (info.readOnly) + if (info.readOnly) { dlg->setUsernameReadOnly(true); - else + } else { dlg->setKnownLogins(knownLogins); + } - if (hasWalletData) + if (hasWalletData) { dlg->setKeepPassword(true); + } - if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid()) + if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid()) { dlg->setDomain(info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).toString()); + } - if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid() && password.isEmpty() && username.isEmpty()) + if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid() + && password.isEmpty() + && username.isEmpty()) { dlg->setAnonymousMode(info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).toBool()); + } #ifndef Q_OS_MACOS dlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(dlg->windowHandle(), request->windowId); #else KWindowSystem::forceActiveWindow(dlg->winId(), 0); #endif qCDebug(category) << "Showing password dialog" << dlg << ", window-id=" << request->windowId; m_authInProgress.insert(dlg, request); dlg->open(); } void KPasswdServer::sendResponse(KPasswdServer::Request *request) { Q_ASSERT(request); if (!request) { return; } qCDebug(category) << "key=" << request->key; if (request->isAsync) { emit queryAuthInfoAsyncResult(request->requestId, m_seqNr, request->info); } else { QByteArray replyData; QDataStream stream2(&replyData, QIODevice::WriteOnly); stream2 << request->info; - QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << replyData << m_seqNr)); + QDBusConnection::sessionBus().send( + request->transaction.createReply(QVariantList() << replyData << m_seqNr)); } // Check all requests in the wait queue. Request *waitRequest; QMutableListIterator it(m_authWait); while (it.hasNext()) { waitRequest = it.next(); if (!hasPendingQuery(waitRequest->key, waitRequest->info)) { const AuthInfoContainer *result = findAuthInfoItem(waitRequest->key, waitRequest->info); QByteArray replyData; QDataStream stream2(&replyData, QIODevice::WriteOnly); KIO::AuthInfo rcinfo; if (!result || result->isCanceled) { waitRequest->info.setModified(false); stream2 << waitRequest->info; } else { updateAuthExpire(waitRequest->key, result, waitRequest->windowId, false); copyAuthInfo(result, rcinfo); stream2 << rcinfo; } if (waitRequest->isAsync) { emit checkAuthInfoAsyncResult(waitRequest->requestId, m_seqNr, rcinfo); } else { - QDBusConnection::sessionBus().send(waitRequest->transaction.createReply(QVariantList() << replyData << m_seqNr)); + QDBusConnection::sessionBus().send( + waitRequest->transaction.createReply(QVariantList() << replyData << m_seqNr)); } delete waitRequest; it.remove(); } } // Re-enable password request processing for the current window id again. m_authPrompted.removeAll(QString::number(request->windowId)); m_authPrompted.removeAll(request->key); - if (!m_authPending.isEmpty()) + if (!m_authPending.isEmpty()) { QTimer::singleShot(0, this, &KPasswdServer::processRequest); + } } void KPasswdServer::passwordDialogDone(int result) { KPasswordDialog *dlg = qobject_cast(sender()); Q_ASSERT(dlg); QScopedPointer request(m_authInProgress.take(dlg)); Q_ASSERT(request); // request should never be nullptr. if (request) { KIO::AuthInfo &info = request->info; const bool bypassCacheAndKWallet = info.getExtraField(AUTHINFO_EXTRAFIELD_BYPASS_CACHE_AND_KWALLET).toBool(); qCDebug(category) << "dialog result=" << result << ", bypassCacheAndKWallet?" << bypassCacheAndKWallet; if (dlg && result == QDialog::Accepted) { Q_ASSERT(dlg); info.username = dlg->username(); info.password = dlg->password(); info.keepPassword = dlg->keepPassword(); - if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid()) + if (info.getExtraField(AUTHINFO_EXTRAFIELD_DOMAIN).isValid()) { info.setExtraField(AUTHINFO_EXTRAFIELD_DOMAIN, dlg->domain()); - if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid()) + } + if (info.getExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS).isValid()) { info.setExtraField(AUTHINFO_EXTRAFIELD_ANONYMOUS, dlg->anonymousMode()); + } // When the user checks "keep password", that means: // * if the wallet is enabled, store it there for long-term, and in kpasswdserver // only for the duration of the window (#92928) // * otherwise store in kpasswdserver for the duration of the KDE session. if (!bypassCacheAndKWallet) { /* NOTE: The following code changes the key under which the auth info is stored in memory if the request url contains a username. e.g. "ftp://user@localhost", but the user changes that username in the password dialog. Since the key generated to store the credential contains the username from the request URL, the key must be updated on such changes. Otherwise, the key will not be found on subsequent requests and the user will be end up being prompted over and over to re-enter the password unnecessarily. */ if (!info.url.userName().isEmpty() && info.username != info.url.userName()) { const QString oldKey(request->key); removeAuthInfoItem(oldKey, info); info.url.setUserName(info.username); request->key = createCacheKey(info); updateCachedRequestKey(m_authPending, oldKey, request->key); updateCachedRequestKey(m_authWait, oldKey, request->key); } #ifdef HAVE_KF5WALLET const bool skipAutoCaching = info.getExtraField(AUTHINFO_EXTRAFIELD_SKIP_CACHING_ON_QUERY).toBool(); if (!skipAutoCaching && info.keepPassword && openWallet(request->windowId)) { - if (storeInWallet(m_wallet, request->key, info)) + if (storeInWallet(m_wallet, request->key, info)) { // password is in wallet, don't keep it in memory after window is closed info.keepPassword = false; + } } #endif addAuthInfoItem(request->key, info, request->windowId, m_seqNr, false); } info.setModified(true); } else { if (!bypassCacheAndKWallet && request->prompt) { addAuthInfoItem(request->key, info, 0, m_seqNr, true); } info.setModified(false); } sendResponse(request.data()); } dlg->deleteLater(); } void KPasswdServer::retryDialogDone(int result) { QDialog *dlg = qobject_cast(sender()); Q_ASSERT(dlg); QScopedPointer request(m_authRetryInProgress.take(dlg)); Q_ASSERT(request); if (request) { if (result == QDialogButtonBox::Yes) { showPasswordDialog(request.take()); } else { // NOTE: If the user simply cancels the retry dialog, we remove the // credential stored under this key because the original attempt to // use it has failed. Otherwise, the failed credential would be cached // and used subsequently. // // TODO: decide whether it should be removed from the wallet too. KIO::AuthInfo &info = request->info; removeAuthInfoItem(request->key, request->info); info.setModified(false); sendResponse(request.data()); } } } void KPasswdServer::windowRemoved(WId id) { bool foundMatch = false; if (!m_authInProgress.isEmpty()) { const qlonglong windowId = (qlonglong)(id); QMutableHashIterator it(m_authInProgress); while (it.hasNext()) { it.next(); if (it.value()->windowId == windowId) { Request *request = it.value(); QObject *obj = it.key(); it.remove(); m_authPrompted.removeAll(QString::number(request->windowId)); m_authPrompted.removeAll(request->key); delete obj; delete request; foundMatch = true; } } } if (!foundMatch && !m_authRetryInProgress.isEmpty()) { const qlonglong windowId = (qlonglong)(id); QMutableHashIterator it(m_authRetryInProgress); while (it.hasNext()) { it.next(); if (it.value()->windowId == windowId) { Request *request = it.value(); QObject *obj = it.key(); it.remove(); delete obj; delete request; } } } } -void KPasswdServer::updateCachedRequestKey(QList &list, const QString &oldKey, const QString &newKey) +void KPasswdServer::updateCachedRequestKey(QList &list, + const QString &oldKey, const QString &newKey) { QListIterator it(list); while (it.hasNext()) { Request *r = it.next(); if (r->key == oldKey) { r->key = newKey; } } }