Add username/password support to QNetworkRequestFactory

Task-number: QTBUG-114717
Change-Id: I8d6beb6f81668dcba59cbaee6044606fb874bad2
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
This commit is contained in:
Juha Vuolle 2023-08-24 10:12:27 +03:00
parent b72701a690
commit 98b240d00a
5 changed files with 166 additions and 0 deletions

View File

@ -349,6 +349,86 @@ void QNetworkRequestFactory::clearBearerToken()
d->bearerToken.clear();
}
/*!
Returns the username set to this factory.
\sa setUserName(), clearUserName(), password()
*/
QString QNetworkRequestFactory::userName() const
{
return d->userName;
}
/*!
Sets the username of this factory to \a userName.
The username is set in the request URL when \l request() is called.
The QRestAccessManager / QNetworkAccessManager will attempt to use
these credentials when the server indicates that authentication
is required.
\sa userName(), clearUserName(), password()
*/
void QNetworkRequestFactory::setUserName(const QString &userName)
{
if (d->userName == userName)
return;
d.detach();
d->userName = userName;
}
/*!
Clears the username set to this factory.
*/
void QNetworkRequestFactory::clearUserName()
{
if (d->userName.isEmpty())
return;
d.detach();
d->userName.clear();
}
/*!
Returns the password set to this factory.
\sa password(), clearPassword(), userName()
*/
QString QNetworkRequestFactory::password() const
{
return d->password;
}
/*!
Sets the password of this factory to \a password.
The password is set in the request URL when \l request() is called.
The QRestAccessManager / QNetworkAccessManager will attempt to use
these credentials when the server indicates that authentication
is required.
\sa password(), clearPassword(), userName()
*/
void QNetworkRequestFactory::setPassword(const QString &password)
{
if (d->password == password)
return;
d.detach();
d->password = password;
}
/*!
Clears the password set to this factory.
\sa password(), setPassword(), userName()
*/
void QNetworkRequestFactory::clearPassword()
{
if (d->password.isEmpty())
return;
d.detach();
d->password.clear();
}
/*!
Sets \a timeout used for transfers.
@ -471,6 +551,10 @@ QUrl QNetworkRequestFactoryPrivate::requestUrl(const QString *path,
QUrl resultUrl = baseUrl;
QUrlQuery resultQuery(providedQuery);
QString basePath = baseUrl.path();
resultUrl.setUserName(userName);
resultUrl.setPassword(password);
// Separate the path and query parameters components on the application-provided path
const QString requestPath{providedPath.path()};
const QUrlQuery pathQueryItems{providedPath};
@ -518,6 +602,8 @@ bool QNetworkRequestFactoryPrivate::equals(
#endif
baseUrl == other.baseUrl &&
bearerToken == other.bearerToken &&
userName == other.userName &&
password == other.password &&
headers.equals(other.headers) &&
queryParameters == other.queryParameters;
}
@ -541,6 +627,8 @@ QDebug operator<<(QDebug debug, const QNetworkRequestFactory &factory)
<< ", queryParameters = " << factory.queryParameters().queryItems()
<< ", bearerToken = " << (factory.bearerToken().isEmpty() ? "(empty)" : "(is set)")
<< ", transferTimeout = " << factory.transferTimeout()
<< ", userName = " << (factory.userName().isEmpty() ? "(empty)" : "(is set)")
<< ", password = " << (factory.password().isEmpty() ? "(empty)" : "(is set)")
#if QT_CONFIG(ssl)
<< ", SSL configuration"
<< (factory.sslConfiguration().isNull() ? " is not set (default)" : " is set")

View File

@ -59,6 +59,14 @@ public:
Q_NETWORK_EXPORT void setBearerToken(const QByteArray &token);
Q_NETWORK_EXPORT void clearBearerToken();
Q_NETWORK_EXPORT QString userName() const;
Q_NETWORK_EXPORT void setUserName(const QString &userName);
Q_NETWORK_EXPORT void clearUserName();
Q_NETWORK_EXPORT QString password() const;
Q_NETWORK_EXPORT void setPassword(const QString &password);
Q_NETWORK_EXPORT void clearPassword();
Q_NETWORK_EXPORT void setTransferTimeout(std::chrono::milliseconds timeout);
Q_NETWORK_EXPORT std::chrono::milliseconds transferTimeout() const;

View File

@ -42,6 +42,8 @@ public:
QUrl baseUrl;
QHttpHeaders headers;
QByteArray bearerToken;
QString userName;
QString password;
QUrlQuery queryParameters;
std::chrono::milliseconds transferTimeout{0};
};

View File

@ -25,6 +25,7 @@ private Q_SLOTS:
void bearerToken();
void operators();
void timeout();
void userInfo();
private:
const QUrl url1{u"http://foo.io"_s};
@ -335,5 +336,31 @@ void tst_QNetworkRequestFactory::timeout()
QCOMPARE(request.transferTimeoutAsDuration(), timeout);
}
void tst_QNetworkRequestFactory::userInfo()
{
QNetworkRequestFactory factory;
QVERIFY(factory.userName().isEmpty());
QVERIFY(factory.password().isEmpty());
const auto uname = u"a_username"_s;
const auto password = u"a_password"_s;
factory.setUserName(uname);
QCOMPARE(factory.userName(), uname);
factory.setPassword(password);
QCOMPARE(factory.password(), password);
// Verify that debug output does not contain password
QString debugOutput;
QDebug debug(&debugOutput);
debug << factory;
QVERIFY(debugOutput.contains("password = (is set)"));
QVERIFY(!debugOutput.contains(password));
factory.clearUserName();
factory.clearPassword();
QVERIFY(factory.userName().isEmpty());
QVERIFY(factory.password().isEmpty());
}
QTEST_MAIN(tst_QNetworkRequestFactory)
#include "tst_qnetworkrequestfactory.moc"

View File

@ -7,6 +7,7 @@
#include <QtNetwork/qrestaccessmanager.h>
#include <QtNetwork/qauthenticator.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qnetworkrequestfactory.h>
#include <QtNetwork/qrestreply.h>
#include <QTest>
@ -35,6 +36,7 @@ private slots:
void networkRequestReply();
void abort();
void authentication();
void userInfo();
void errors();
void body();
void json();
@ -513,6 +515,45 @@ void tst_QRestAccessManager::authentication()
QCOMPARE(serverSideRequest.headers["Authorization"_ba], "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba);
}
void tst_QRestAccessManager::userInfo()
{
// Tests setting of username and password into the request factory
using ReplyPtr = std::unique_ptr<QRestReply, QScopedPointerDeleteLater>;
QRestAccessManager manager;
manager.setDeletesRepliesOnFinished(false);
HttpTestServer server;
QTRY_VERIFY(server.isListening());
QNetworkRequestFactory factory(server.url());
factory.setUserName(u"a_user"_s);
const auto password = u"a_password"_s;
factory.setPassword(password);
HttpData serverSideRequest;
server.setHandler([&](HttpData request, HttpData& response, ResponseControl&) {
if (!request.headers.contains("Authorization"_ba)) {
response.status = 401;
response.headers.insert("WWW-Authenticate: "_ba, "Basic realm=\"secret_place\""_ba);
} else {
response.status = 200;
}
serverSideRequest = request; // store for checking later the 'Authorization' header value
});
ReplyPtr reply(manager.get(factory.request()));
QTRY_VERIFY(reply.get()->isFinished());
QVERIFY(reply.get()->isSuccess());
QCOMPARE(reply.get()->httpStatus(), 200);
QCOMPARE(serverSideRequest.headers["Authorization"_ba], "Basic YV91c2VyOmFfcGFzc3dvcmQ="_ba);
// Verify that debug output does not contain password
QString debugOutput;
QDebug debug(&debugOutput);
debug << factory;
QVERIFY(debugOutput.contains("password = (is set)"));
QVERIFY(!debugOutput.contains(password));
}
#define VERIFY_HTTP_ERROR_STATUS(STATUS) \
serverSideResponse.status = STATUS; \
reply = manager.get(request); \