diff --git a/src/kpasswdserver/kpasswdserver.cpp b/src/kpasswdserver/kpasswdserver.cpp index ed20fbd8..c5da2603 100644 --- a/src/kpasswdserver/kpasswdserver.cpp +++ b/src/kpasswdserver/kpasswdserver.cpp @@ -1,1106 +1,1106 @@ /* 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"); +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) 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 ) 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() ) ) 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 ) { //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 ) 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)); Q_FOREACH(const Request *request, 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))) { QMap knownLogins; if (openWallet(windowId)) { 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))) { QMap knownLogins; if (openWallet(windowId)) { 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) { KIO::AuthInfo info; QDataStream stream(data); stream >> info; 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) 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) { 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; 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< QString, AuthInfoContainerList* > dictIterator(m_authDict); while (dictIterator.hasNext()) { dictIterator.next(); AuthInfoContainerList *authList = dictIterator.value(); if (!authList) continue; Q_FOREACH(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) << "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 ) 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); 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)); 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); Q_FOREACH(AuthInfoContainer *current, *authList) { if (current->expire == AuthInfoContainer::expTime && static_cast(time(nullptr)) > current->expireTime) { authList->removeOne(current); delete current; continue; } if (info.verifyPath) { QString path1 = current->directory; 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)) return current; // TODO: Update directory info, } } } return nullptr; } void KPasswdServer::removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info) { AuthInfoContainerList *authList = m_authDict.value(key); if (!authList) return; Q_FOREACH(AuthInfoContainer *current, *authList) { if (current->info.realmValue == info.realmValue) { authList->removeOne(current); delete current; } } if (authList->isEmpty()) { delete m_authDict.take(key); } } 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; AuthInfoContainerList *authList = m_authDict.value(key); if (!authList) { authList = new AuthInfoContainerList; m_authDict.insert(key, authList); } AuthInfoContainer *authItem = nullptr; Q_FOREACH(AuthInfoContainer* current, *authList) { if (current->info.realmValue == info.realmValue) { authList->removeAll(current); authItem = current; break; } } 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) { AuthInfoContainer *current = const_cast(auth); Q_ASSERT(current); 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)) 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) 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 )) ) { // 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 ); } #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()) 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()) dlg->setWindowTitle( i18n("Authentication Dialog") ); else dlg->setWindowTitle( info.caption ); if ( !info.comment.isEmpty() ) dlg->addCommentLine( info.commentLabel, info.comment ); if ( !password.isEmpty() ) dlg->setPassword( password ); if (info.readOnly) dlg->setUsernameReadOnly( true ); else dlg->setKnownLogins( knownLogins ); if (hasWalletData) dlg->setKeepPassword( true ); 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()) 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)); } // 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)); } 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()) 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 ()) info.setExtraField(AUTHINFO_EXTRAFIELD_DOMAIN, dlg->domain()); 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 ) ) // 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) { QListIterator it (list); while (it.hasNext()) { Request* r = it.next(); if (r->key == oldKey) { r->key = newKey; } } }