Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
find_package(KF6Wallet ${KF_DEP_VERSION})
|
||||
|
||||
# tell what is missing without wallet
|
||||
set_package_properties(KF6Wallet PROPERTIES DESCRIPTION "Safe desktop-wide storage for passwords"
|
||||
TYPE OPTIONAL
|
||||
PURPOSE "Required to have permanent storage of passwords for kpasswdserver"
|
||||
)
|
||||
|
||||
if (KF6Wallet_FOUND)
|
||||
add_definitions(-DHAVE_KF6WALLET)
|
||||
set(WALLET_LIB KF6::Wallet)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(autotests)
|
||||
endif()
|
||||
|
||||
kcoreaddons_add_plugin(kiod_kpasswdserver
|
||||
INSTALL_NAMESPACE "kf6/kiod"
|
||||
)
|
||||
set_target_properties(kiod_kpasswdserver PROPERTIES
|
||||
OUTPUT_NAME kpasswdserver
|
||||
)
|
||||
|
||||
set(kiod_kpasswdserver_dbus_SRCS)
|
||||
qt_add_dbus_adaptor(kiod_kpasswdserver_dbus_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../core/org.kde.KPasswdServer.xml kpasswdserver.h KPasswdServer)
|
||||
|
||||
target_sources(kiod_kpasswdserver PRIVATE
|
||||
${kiod_kpasswdserver_dbus_SRCS}
|
||||
kpasswdserver.cpp
|
||||
kiod_kpasswdserver.cpp
|
||||
)
|
||||
|
||||
ecm_qt_export_logging_category(
|
||||
IDENTIFIER category
|
||||
CATEGORY_NAME kf.kio.kpasswdserver
|
||||
OLD_CATEGORY_NAMES org.kde.kio.kpasswdserver
|
||||
DESCRIPTION "KPasswdServer (KIO)"
|
||||
EXPORT KIO
|
||||
)
|
||||
|
||||
target_link_libraries(kiod_kpasswdserver
|
||||
KF6::DBusAddons # KDED Module
|
||||
${DBUS_LIB}
|
||||
KF6::KIOCore
|
||||
KF6::WidgetsAddons # KPasswordDialog
|
||||
KF6::I18n
|
||||
KF6::WindowSystem
|
||||
${WALLET_LIB}
|
||||
)
|
||||
|
||||
kdbusaddons_generate_dbus_service_file(kiod6 org.kde.kpasswdserver6 ${KDE_INSTALL_FULL_LIBEXECDIR_KF})
|
||||
@@ -0,0 +1,74 @@
|
||||
Credentials storage duration
|
||||
============================
|
||||
How long credentials are stored depends on a couple of factors. The first is
|
||||
whether a window-id (see QWidget::WId) was provided when the password request
|
||||
was made. The other is the state of KIO::AuthInfo's keepPassword flag.
|
||||
|
||||
If the window-id parameter is missing, the credentials are stored for the
|
||||
entire duration of the current KDE session. Otherwise, they are only kept
|
||||
until the application(s) associated with the window-id exit.
|
||||
|
||||
The "keepPassword" flag on the other hand only matters for caching credentials
|
||||
in a persistent storage such as KWallet. If this flag is set to false, then
|
||||
credentials are only kept in memory based on the value of window-id as discussed
|
||||
above. See the documentation for the "keepPassword" property in KIO::AuthInfo
|
||||
for more details.
|
||||
|
||||
Dawit A. <adawit@kde.org>
|
||||
|
||||
Sequence numbers
|
||||
================
|
||||
The idea is that whenever the user is queried for a password this
|
||||
login/pw combination gets a seq-nr. When a worker needs a login/pw
|
||||
it asks kpasswdserver and sends along the last seqnr it received. If
|
||||
this seqnr is older (lower) than the seq nr of the login/pw
|
||||
combination stored in kpasswdserver then apparently the user has
|
||||
already been prompted for a new login/pw combination since the last
|
||||
time this worker asked for a login/pw and therefore it is not necessary
|
||||
to prompt the user again but kpassword will send the KIO worker this
|
||||
new login/pw combination. If this new combination fails as well the
|
||||
user is being prompted for a new login/pw combo since the one stored
|
||||
in kpasswdserver doesn't work.
|
||||
|
||||
Let me try to draw the situation I had in mind when writing this:
|
||||
|
||||
Worker1 Worker2 kpasswdserver
|
||||
Asks for auth
|
||||
asks user for login/pw (1)
|
||||
sends login/pw (1) to ftp site
|
||||
Asks for auth
|
||||
sends back login/pw (1)
|
||||
sends login/pw (1) to ftp site
|
||||
gets login error,
|
||||
asks for new auth
|
||||
sends along seq.nr 1
|
||||
seq.nr 1 == (1) -->
|
||||
asks user for new login/pw (2)
|
||||
sends login/pw (2) to ftp site
|
||||
gets login error,
|
||||
asks for new auth
|
||||
sends along seq.nr 1
|
||||
seq.nr 1 < (2) -->
|
||||
don't ask user for new login/pw
|
||||
but send back login/pw (2) without asking
|
||||
sends login/pw (2) to ftp site
|
||||
|
||||
|
||||
Actually, I had mostly http in mind, and not so much ftp. In http you
|
||||
typically try without password first, and only when you get an
|
||||
authentication error you ask for a password. The above scenario is
|
||||
then suddenly a lot more common than with ftp because it can happen
|
||||
that you have 4 requests /workers who all discover at about the
|
||||
same time that they need to have authentication credentials. The
|
||||
above scenario (and the seq. nrs) is to prevent that you get 4 login
|
||||
dialogs in such case.
|
||||
|
||||
Now the assumption in this all, looking back on it, seems to be that
|
||||
when you ask for the same auth credentials twice in a row, it must be
|
||||
that the credentials issued the first time where wrong, and you will
|
||||
be prompted again. But if the user goes to ftp-site1, then
|
||||
ftp-site2 and then back to ftp-site1 again, the credentials for ftp-site1
|
||||
are still valid. This is why we reset the seq.nr stored in the KIO worker
|
||||
to 0 whenever the KIO worker switches hosts (or logins).
|
||||
|
||||
Waldo Bastian <bastian@kde.org>
|
||||
@@ -0,0 +1,22 @@
|
||||
include(ECMAddTests)
|
||||
|
||||
include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/.. )
|
||||
|
||||
set (kpasswdservertest_dbus_SRCS)
|
||||
qt_add_dbus_adaptor(kpasswdservertest_dbus_SRCS ../../core/org.kde.KPasswdServer.xml kpasswdserver.h KPasswdServer)
|
||||
|
||||
ecm_add_test(
|
||||
${kpasswdservertest_dbus_SRCS}
|
||||
kpasswdservertest.cpp
|
||||
../kpasswdserver.cpp
|
||||
TEST_NAME kpasswdservertest
|
||||
LINK_LIBRARIES
|
||||
KF6::DBusAddons
|
||||
KF6::KIOCore
|
||||
KF6::WidgetsAddons
|
||||
KF6::WindowSystem
|
||||
KF6::I18n
|
||||
Qt6::Core
|
||||
Qt6::Test
|
||||
${WALLET_LIB}
|
||||
)
|
||||
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2010 David Faure <faure@kde.org>
|
||||
SPDX-FileCopyrightText: 2012 Dawit Alemayehu <adawit@kde.org>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
*/
|
||||
|
||||
#include <kpasswdserver.h>
|
||||
|
||||
#include <KPasswordDialog>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
// 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<QDialogButtonBox::StandardButton>();
|
||||
qRegisterMetaType<QDialog::DialogCode>();
|
||||
}
|
||||
|
||||
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"));
|
||||
info.keepPassword = true;
|
||||
|
||||
// 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, &KPasswdServer::queryAuthInfoAsyncResult);
|
||||
const qlonglong windowId = 42;
|
||||
const qlonglong seqNr = 2;
|
||||
const qlonglong id = server.queryAuthInfoAsync(info,
|
||||
QStringLiteral("<NoAuthPrompt>"), // magic string to avoid a dialog
|
||||
windowId,
|
||||
seqNr,
|
||||
16 /*usertime*/);
|
||||
|
||||
// Before it is processed, do a check, it will reply delayed.
|
||||
QSignalSpy spyCheck(&server, &KPasswdServer::checkAuthInfoAsyncResult);
|
||||
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, &KPasswdServer::queryAuthInfoAsyncResult).wait(1000));
|
||||
QCOMPARE(spyQuery.count(), 1);
|
||||
QCOMPARE(spyQuery[0][0].toLongLong(), id);
|
||||
KIO::AuthInfo result = spyQuery[0][2].value<KIO::AuthInfo>();
|
||||
|
||||
// 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<KIO::AuthInfo>();
|
||||
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"));
|
||||
info.keepPassword = true;
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
void testAcceptRetryDialog()
|
||||
{
|
||||
QSKIP("TODO testAcceptRetryDialog doesn't pass FIXME");
|
||||
|
||||
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"));
|
||||
}
|
||||
|
||||
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");
|
||||
authInfo.keepPassword = true;
|
||||
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<KIO::AuthInfo> 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<KIO::AuthInfo> results;
|
||||
concurrentQueryAuthWithDialog(server, authInfos, filledInfo, results);
|
||||
}
|
||||
|
||||
void testConcurrentCheckAuth()
|
||||
{
|
||||
KPasswdServer server(this);
|
||||
server.setWalletDisabled(true);
|
||||
|
||||
QList<KIO::AuthInfo> 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<KIO::AuthInfo> 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, &KPasswdServer::checkAuthInfoAsyncResult);
|
||||
const qlonglong windowId = 42;
|
||||
const qlonglong id = server.checkAuthInfoAsync(info, windowId, 17 /*usertime*/);
|
||||
QCOMPARE(id, 0LL); // always
|
||||
if (spy.isEmpty()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::checkAuthInfoAsyncResult).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<KIO::AuthInfo>();
|
||||
}
|
||||
|
||||
void queryAuth(KPasswdServer &server, const KIO::AuthInfo &info, KIO::AuthInfo &result)
|
||||
{
|
||||
QSignalSpy spy(&server, &KPasswdServer::queryAuthInfoAsyncResult);
|
||||
const qlonglong windowId = 42;
|
||||
const qlonglong seqNr = 2;
|
||||
const qlonglong id = server.queryAuthInfoAsync(info,
|
||||
QStringLiteral("<NoAuthPrompt>"), // magic string to avoid a dialog
|
||||
windowId,
|
||||
seqNr,
|
||||
16 /*usertime*/);
|
||||
QVERIFY(id >= 0); // requestId, ever increasing
|
||||
if (spy.isEmpty()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::queryAuthInfoAsyncResult).wait(1000));
|
||||
}
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QCOMPARE(spy[0][0].toLongLong(), id);
|
||||
// QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr
|
||||
result = spy[0][2].value<KIO::AuthInfo>();
|
||||
}
|
||||
|
||||
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, &KPasswdServer::queryAuthInfoAsyncResult);
|
||||
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) {
|
||||
auto checkRetryFunc = [this, retryButton]() {
|
||||
checkRetryDialog(retryButton);
|
||||
};
|
||||
// Retry dialog only knows Yes/No
|
||||
QMetaObject::invokeMethod(this, checkRetryFunc, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
if (!isCancelRetryDialogTest) {
|
||||
auto checkFillFunc = [this, info, filledInfo, code]() {
|
||||
checkAndFillDialog(info, filledInfo, code);
|
||||
};
|
||||
QMetaObject::invokeMethod(this, checkFillFunc, Qt::QueuedConnection);
|
||||
}
|
||||
// Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too...
|
||||
server.processRequest();
|
||||
if (spy.isEmpty()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::queryAuthInfoAsyncResult).wait(1000));
|
||||
}
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QCOMPARE(spy[0][0].toLongLong(), id);
|
||||
// QCOMPARE(spy[0][1].toLongLong(), 3LL); // seqNr
|
||||
result = spy[0][2].value<KIO::AuthInfo>();
|
||||
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<KIO::AuthInfo> &infos,
|
||||
const KIO::AuthInfo &filledInfo,
|
||||
QList<KIO::AuthInfo> &results,
|
||||
QDialog::DialogCode code = QDialog::Accepted)
|
||||
{
|
||||
QSignalSpy spy(&server, &KPasswdServer::queryAuthInfoAsyncResult);
|
||||
const qlonglong windowId = 42;
|
||||
qlonglong seqNr = 0;
|
||||
QList<qlonglong> 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());
|
||||
auto checkFillFunc = [this, first = infos.first(), filledInfo, code]() {
|
||||
checkAndFillDialog(first, filledInfo, code);
|
||||
};
|
||||
QMetaObject::invokeMethod(this, checkFillFunc, Qt::QueuedConnection);
|
||||
|
||||
// Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too...
|
||||
server.processRequest();
|
||||
while (spy.count() < infos.count()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::queryAuthInfoAsyncResult).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<KIO::AuthInfo>();
|
||||
QCOMPARE(result.username, filledInfo.username);
|
||||
QCOMPARE(result.password, filledInfo.password);
|
||||
QCOMPARE(result.isModified(), code == QDialog::Accepted);
|
||||
results << result;
|
||||
}
|
||||
}
|
||||
|
||||
void concurrentCheckAuthWithDialog(KPasswdServer &server,
|
||||
const QList<KIO::AuthInfo> &infos,
|
||||
const KIO::AuthInfo &filledInfo,
|
||||
QList<KIO::AuthInfo> &results,
|
||||
QDialog::DialogCode code = QDialog::Accepted)
|
||||
{
|
||||
QSignalSpy spy(&server, &KPasswdServer::queryAuthInfoAsyncResult);
|
||||
const qlonglong windowId = 42;
|
||||
qlonglong seqNr = 0;
|
||||
QList<qlonglong> idList;
|
||||
|
||||
QListIterator<KIO::AuthInfo> 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());
|
||||
auto checkAndFillFunc = [this, first = infos.first(), filledInfo, code]() {
|
||||
checkAndFillDialog(first, filledInfo, code);
|
||||
};
|
||||
QMetaObject::invokeMethod(this, checkAndFillFunc, Qt::QueuedConnection);
|
||||
|
||||
// Force KPasswdServer to process the request now, otherwise the checkAndFillDialog needs a timer too...
|
||||
server.processRequest();
|
||||
if (spy.isEmpty()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::queryAuthInfoAsyncResult).wait(1000));
|
||||
}
|
||||
|
||||
while ((spy.count() - 1) < infos.count()) {
|
||||
QVERIFY(QSignalSpy(&server, &KPasswdServer::checkAuthInfoAsyncResult).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<KIO::AuthInfo>();
|
||||
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<QWidget *> widgetsList = QApplication::topLevelWidgets();
|
||||
for (QWidget *widget : widgetsList) {
|
||||
if (KPasswordDialog *dialog = qobject_cast<KPasswordDialog *>(widget)) {
|
||||
qDebug() << "Found dialog" << dialog;
|
||||
if (code == QDialog::Accepted) {
|
||||
QCOMPARE(dialog->username(), getUserNameFrom(info));
|
||||
QCOMPARE(dialog->password(), info.password);
|
||||
dialog->setUsername(filledInfo.username);
|
||||
dialog->setPassword(filledInfo.password);
|
||||
qDebug() << "Filled dialog with" << filledInfo.username << filledInfo.password;
|
||||
}
|
||||
dialog->done(code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qWarning() << "No KPasswordDialog found!";
|
||||
}
|
||||
|
||||
void checkRetryDialog(QDialogButtonBox::StandardButton code = s_buttonYes)
|
||||
{
|
||||
const QList<QWidget *> widgetsList = QApplication::topLevelWidgets();
|
||||
for (QWidget *widget : widgetsList) {
|
||||
QDialog *dialog = qobject_cast<QDialog *>(widget);
|
||||
if (dialog && !dialog->inherits("KPasswordDialog")) {
|
||||
qDebug() << "Closing dialog" << dialog << "with code" << code;
|
||||
dialog->done(code);
|
||||
return;
|
||||
}
|
||||
}
|
||||
qWarning() << "No retry dialog found";
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(KPasswdServerTest)
|
||||
|
||||
#include "kpasswdservertest.moc"
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2015 Alex Richardson <arichardson.kde@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "kpasswdserver.h"
|
||||
|
||||
#include <KPluginFactory>
|
||||
|
||||
K_PLUGIN_CLASS_WITH_JSON(KPasswdServer, "kpasswdserver.json")
|
||||
|
||||
#include "kiod_kpasswdserver.moc"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
This file is part of the KDE Password Server
|
||||
SPDX-FileCopyrightText: 2002 Waldo Bastian (bastian@kde.org)
|
||||
SPDX-FileCopyrightText: 2012 Dawit Alemayehu (adawit@kde.org)
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-only
|
||||
*/
|
||||
|
||||
// KDE Password Server
|
||||
|
||||
#ifndef KPASSWDSERVER_H
|
||||
#define KPASSWDSERVER_H
|
||||
|
||||
#include <QDBusContext>
|
||||
#include <QDBusMessage>
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include <KDEDModule>
|
||||
#include <kio/authinfo.h>
|
||||
|
||||
class KMessageDialog;
|
||||
class KPasswordDialog;
|
||||
|
||||
namespace KWallet
|
||||
{
|
||||
class Wallet;
|
||||
}
|
||||
|
||||
class KPasswdServer : public KDEDModule, protected QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KPasswdServer(QObject *parent, const QList<QVariant> & = QList<QVariant>());
|
||||
~KPasswdServer() override;
|
||||
|
||||
// Called by the unit test
|
||||
void setWalletDisabled(bool d)
|
||||
{
|
||||
m_walletDisabled = d;
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
qlonglong checkAuthInfoAsync(KIO::AuthInfo, qlonglong, qlonglong);
|
||||
qlonglong queryAuthInfoAsync(const KIO::AuthInfo &, const QString &, qlonglong, qlonglong, qlonglong);
|
||||
void addAuthInfo(const KIO::AuthInfo &, qlonglong);
|
||||
void removeAuthInfo(const QString &host, const QString &protocol, const QString &user);
|
||||
|
||||
// legacy methods provided for compatibility with old clients
|
||||
QByteArray checkAuthInfo(const QByteArray &, qlonglong, qlonglong);
|
||||
QByteArray queryAuthInfo(const QByteArray &, const QString &, qlonglong, qlonglong, qlonglong);
|
||||
void addAuthInfo(const QByteArray &, qlonglong);
|
||||
|
||||
void processRequest();
|
||||
// Remove all authentication info associated with windowId
|
||||
void removeAuthForWindowId(qlonglong windowId);
|
||||
|
||||
Q_SIGNALS:
|
||||
void checkAuthInfoAsyncResult(qlonglong requestId, qlonglong seqNr, const KIO::AuthInfo &);
|
||||
void queryAuthInfoAsyncResult(qlonglong requestId, qlonglong seqNr, const KIO::AuthInfo &);
|
||||
|
||||
private Q_SLOTS:
|
||||
void passwordDialogDone(int result, KPasswordDialog *sender);
|
||||
void retryDialogDone(int result, KMessageDialog *sender);
|
||||
void windowRemoved(WId);
|
||||
|
||||
private:
|
||||
struct AuthInfoContainer {
|
||||
AuthInfoContainer()
|
||||
{
|
||||
}
|
||||
|
||||
KIO::AuthInfo info;
|
||||
QString directory;
|
||||
|
||||
enum {
|
||||
expNever,
|
||||
expWindowClose,
|
||||
expTime
|
||||
} expire;
|
||||
QList<qlonglong> windowList;
|
||||
qulonglong expireTime = expNever;
|
||||
qlonglong seqNr = 0;
|
||||
|
||||
bool isCanceled = false;
|
||||
|
||||
struct Sorter {
|
||||
bool operator()(const AuthInfoContainer &n1, const AuthInfoContainer &n2) const;
|
||||
};
|
||||
};
|
||||
|
||||
struct Request {
|
||||
bool isAsync; // true for async requests
|
||||
qlonglong requestId; // set for async requests only
|
||||
QDBusMessage transaction; // set for sync requests only
|
||||
QString key;
|
||||
KIO::AuthInfo info;
|
||||
QString errorMsg;
|
||||
qlonglong windowId;
|
||||
qlonglong seqNr;
|
||||
bool prompt;
|
||||
};
|
||||
|
||||
QString createCacheKey(const KIO::AuthInfo &info);
|
||||
const AuthInfoContainer *findAuthInfoItem(const QString &key, const KIO::AuthInfo &info);
|
||||
void removeAuthInfoItem(const QString &key, const KIO::AuthInfo &info);
|
||||
void addAuthInfoItem(const QString &key, const KIO::AuthInfo &info, qlonglong windowId, qlonglong seqNr, bool canceled);
|
||||
void copyAuthInfo(const AuthInfoContainer *, KIO::AuthInfo &);
|
||||
void updateAuthExpire(const QString &key, const AuthInfoContainer *, qlonglong windowId, bool keep);
|
||||
|
||||
#ifdef HAVE_KF6WALLET
|
||||
bool openWallet(qlonglong windowId);
|
||||
#endif
|
||||
|
||||
bool hasPendingQuery(const QString &key, const KIO::AuthInfo &info);
|
||||
void sendResponse(Request *request);
|
||||
void showPasswordDialog(Request *request);
|
||||
void updateCachedRequestKey(QList<Request *> &, const QString &oldKey, const QString &newKey);
|
||||
|
||||
using AuthInfoContainerList = QList<AuthInfoContainer>;
|
||||
QHash<QString, AuthInfoContainerList *> m_authDict;
|
||||
|
||||
QList<Request *> m_authPending;
|
||||
QList<Request *> m_authWait;
|
||||
QHash<int, QStringList> mWindowIdList;
|
||||
QHash<QObject *, Request *> m_authInProgress;
|
||||
QHash<QObject *, Request *> m_authRetryInProgress;
|
||||
QStringList m_authPrompted;
|
||||
KWallet::Wallet *m_wallet;
|
||||
bool m_walletDisabled;
|
||||
qlonglong m_seqNr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"X-KDE-DBus-ServiceName": "org.kde.kpasswdserver6"
|
||||
}
|
||||
Reference in New Issue
Block a user