summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@qt.io>2024-11-29 12:50:13 +0200
committerJuha Vuolle <juha.vuolle@qt.io>2024-12-04 14:27:07 +0200
commitb1e67440a6c2fbccdecd599def6f9822a31d5b07 (patch)
tree11ee156cd1150e0cf46f519256efb16131a4f3f9
parent201a224441c2dc83e54cbc606b04ea9006ada7f8 (diff)
Improve callback/redirect_uri hostname setting
Setting hostname part of redirect uri (callback) is important part to get correct, because authorization servers often expect a verbatim match between what has been registered, and what is sent as part of authorization request. This commit clarifies and improves specifying the hostname part. Historically the callback host was fixed to "127.0.0.1" which has its drawbacks (and can be outright wrong). The situation was later improved by using "localhost" hostname, which correctly maps to both IPv4 and IPv6 interfaces. However, there are authorization servers which require the use of IP literals, and reject "localhost". To address these issues, this commit consists of: - Map Any, AnyIPv4, and AnyIPv6 to "localhost". This mapping is logical because "localhost" will work then independent of what the actual used address is - Map IPv4 and IPv6 loopback addresses (LocalHost, LocalHostIPv6) to their IP literals 127.0.0.1 and ::1 These are well-known IP literal addresses, and mapping them to "localhost" does not bring an advantage - If user has provided a string literal, use that directly - Change default address to "LocalHost", which maps to hostname "127.0.0.1". This, in part, undoes the changed behavior where the default changed to "localhost". With this change, users that don't define callback address shouldn't notice a difference to the very original behavior All in all these changes should improve the compatibility with various authorization servers and provide more flexibility to users. Amends: fd49b7f6543e7b49be7847624c64ee86c4272ccd Amends: 4e03167088181bf513adcfb8aac93fb8efb3f420 [ChangeLog][QOAuthHttpServerReplyHandler] Changed and clarified callback hostname handling (especially localhost vs. 127.0.0.1) Pick-to: 6.8 Fixes: QTBUG-130159 Change-Id: I25dfb996d10f95fe60bdb4f46ea848edcb2528be Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
-rw-r--r--examples/oauth/redditclient/redditmodel.cpp2
-rw-r--r--src/oauth/qoauthhttpserverreplyhandler.cpp53
-rw-r--r--src/oauth/qoauthhttpserverreplyhandler_p.h1
-rw-r--r--tests/auto/oauthhttpserverreplyhandler/tst_oauthhttpserverreplyhandler.cpp51
4 files changed, 86 insertions, 21 deletions
diff --git a/examples/oauth/redditclient/redditmodel.cpp b/examples/oauth/redditclient/redditmodel.cpp
index 309ab8c..514a92c 100644
--- a/examples/oauth/redditclient/redditmodel.cpp
+++ b/examples/oauth/redditclient/redditmodel.cpp
@@ -29,7 +29,7 @@ RedditModel::RedditModel(const QString &clientId, QObject *parent) :
redditApi.setBaseUrl(QUrl(hotUrl));
- auto replyHandler = new QOAuthHttpServerReplyHandler(1337, this);
+ auto replyHandler = new QOAuthHttpServerReplyHandler(QHostAddress::Any, 1337, this);
oauth2.setReplyHandler(replyHandler);
oauth2.setAuthorizationUrl(QUrl(authorizationUrl));
oauth2.setAccessTokenUrl(QUrl(accessTokenUrl));
diff --git a/src/oauth/qoauthhttpserverreplyhandler.cpp b/src/oauth/qoauthhttpserverreplyhandler.cpp
index 4bd1c2d..7381899 100644
--- a/src/oauth/qoauthhttpserverreplyhandler.cpp
+++ b/src/oauth/qoauthhttpserverreplyhandler.cpp
@@ -72,11 +72,20 @@ using namespace Qt::StringLiterals;
\section1 IPv4 and IPv6
- Currently if the handler is a loopback address, IPv4 any address,
- or IPv6 any address, the used callback is in the form of
- \e {http://localhost:{port}/{path}}. Otherwise, for specific
- IP addresses, the actual IP literal is used. For instance
- \e {http://192.168.0.2:{port}/{path}} in the case of IPv4.
+ If the handler is an \e any address handler
+ (\l {QHostAddress::SpecialAddress}{AnyIPv4, AnyIPv6, or Any}),
+ the used callback is in the form of \c {http://localhost:{port}/{path}}.
+ Handler will first attempt to listen on IPv4 loopback address,
+ and then on IPv6. \c {localhost} is used because it resolves correctly
+ on both IPv4 and IPv6 interfaces.
+
+ For loopback addresses
+ (\l {QHostAddress::SpecialAddress}{LocalHost or LocalHostIPv6})
+ the IP literals (\c {127.0.0.1} and \c {::1}) are used.
+
+ For specific IP addresses the provided IP literal is used directly,
+ for instance:
+ \e {http://192.168.0.123:{port}/{path}} in the case of an IPv4 address.
\section1 HTTP and HTTPS Callbacks
@@ -85,7 +94,7 @@ using namespace Qt::StringLiterals;
providing an appropriate \l QSslConfiguration when calling
\l {listen(const QSslConfiguration &, const QHostAddress &, quint16)}{listen()}.
Internally the handler will then use \l QSslServer, and the callback
- (redirect URL) will be of the form \e {https://localhost:{port}/{path}}.
+ (redirect URL) will be of the form \e {https://{host}:{port}/{path}}.
Following example illustrates this:
\snippet src_oauth_replyhandlers.cpp localhost-https-scheme-setup
@@ -137,17 +146,25 @@ QString QOAuthHttpServerReplyHandlerPrivate::callback() const
#endif
url.setPort(callbackPort);
url.setPath(path);
+ url.setHost(callbackHost());
+ return url.toString(QUrl::EncodeSpaces | QUrl::EncodeUnicode | QUrl::EncodeDelimiters
+ | QUrl::EncodeReserved);
+}
- // convert Any and Localhost addresses to "localhost"
- if (callbackAddress.isLoopback() || callbackAddress == QHostAddress::AnyIPv4
- || callbackAddress == QHostAddress::Any || callbackAddress == QHostAddress::AnyIPv6) {
- url.setHost(u"localhost"_s);
+QString QOAuthHttpServerReplyHandlerPrivate::callbackHost() const
+{
+ QString host;
+ if (callbackAddress == QHostAddress::AnyIPv4 || callbackAddress == QHostAddress::Any
+ || callbackAddress == QHostAddress::AnyIPv6) {
+ // Convert Any addresses to "localhost"
+ host = u"localhost"_s;
} else {
- url.setHost(callbackAddress.toString());
+ // For other than Any addresses, use QHostAddress::toString() which returns an
+ // IP literal. This includes user-provided addresses, as well as special addresses
+ // such as LocalHost (127.0.0.1) and LocalHostIPv6 (::1)
+ host = callbackAddress.toString();
}
-
- return url.toString(QUrl::EncodeSpaces | QUrl::EncodeUnicode | QUrl::EncodeDelimiters
- | QUrl::EncodeReserved);
+ return host;
}
void QOAuthHttpServerReplyHandlerPrivate::_q_clientConnected()
@@ -376,23 +393,23 @@ bool QOAuthHttpServerReplyHandlerPrivate::QHttpRequest::readHeader(QTcpSocket *s
/*!
Constructs a QOAuthHttpServerReplyHandler object using \a parent as a
parent object. Calls \l {listen()} with port \c 0 and address
- \l {QHostAddress::SpecialAddress}{Null}.
+ \l {QHostAddress::SpecialAddress}{LocalHost}.
\sa listen()
*/
QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(QObject *parent) :
- QOAuthHttpServerReplyHandler(QHostAddress::Null, 0, parent)
+ QOAuthHttpServerReplyHandler(QHostAddress::LocalHost, 0, parent)
{}
/*!
Constructs a QOAuthHttpServerReplyHandler object using \a parent as a
parent object. Calls \l {listen()} with \a port and address
- \l {QHostAddress::SpecialAddress}{Null}.
+ \l {QHostAddress::SpecialAddress}{LocalHost}.
\sa listen()
*/
QOAuthHttpServerReplyHandler::QOAuthHttpServerReplyHandler(quint16 port, QObject *parent) :
- QOAuthHttpServerReplyHandler(QHostAddress::Null, port, parent)
+ QOAuthHttpServerReplyHandler(QHostAddress::LocalHost, port, parent)
{}
/*!
diff --git a/src/oauth/qoauthhttpserverreplyhandler_p.h b/src/oauth/qoauthhttpserverreplyhandler_p.h
index 57b8ff6..104ed68 100644
--- a/src/oauth/qoauthhttpserverreplyhandler_p.h
+++ b/src/oauth/qoauthhttpserverreplyhandler_p.h
@@ -36,6 +36,7 @@ public:
~QOAuthHttpServerReplyHandlerPrivate();
QString callback() const;
+ QString callbackHost() const;
QTcpServer *httpServer = nullptr;
QString text;
diff --git a/tests/auto/oauthhttpserverreplyhandler/tst_oauthhttpserverreplyhandler.cpp b/tests/auto/oauthhttpserverreplyhandler/tst_oauthhttpserverreplyhandler.cpp
index e811283..932c05d 100644
--- a/tests/auto/oauthhttpserverreplyhandler/tst_oauthhttpserverreplyhandler.cpp
+++ b/tests/auto/oauthhttpserverreplyhandler/tst_oauthhttpserverreplyhandler.cpp
@@ -36,6 +36,8 @@ private Q_SLOTS:
#ifndef QT_NO_SSL
void localhostHttps();
#endif
+ void callbackHost_data();
+ void callbackHost();
private:
QString testDataDir;
@@ -219,7 +221,7 @@ void tst_QOAuthHttpServerReplyHandler::callbackCaching()
{
QOAuthHttpServerReplyHandler replyHandler;
constexpr auto callbackPath = "/foo"_L1;
- constexpr auto callbackHost = "localhost"_L1;
+ constexpr auto callbackHost = "127.0.0.1"_L1;
QVERIFY(replyHandler.isListening());
replyHandler.setCallbackPath(callbackPath);
@@ -234,7 +236,7 @@ void tst_QOAuthHttpServerReplyHandler::callbackCaching()
QCOMPARE(callback.path(), callbackPath);
QCOMPARE(callback.host(), callbackHost);
- replyHandler.listen();
+ replyHandler.listen({QHostAddress::SpecialAddress::LocalHost});
QVERIFY(replyHandler.isListening());
callback = replyHandler.callback();
QCOMPARE(callback.path(), callbackPath);
@@ -457,6 +459,51 @@ void tst_QOAuthHttpServerReplyHandler::localhostHttps()
}
#endif // QT_NO_SSL
+void tst_QOAuthHttpServerReplyHandler::callbackHost_data()
+{
+ QTest::addColumn<QHostAddress>("hostAddress");
+ QTest::addColumn<QUrl>("expectedCallback");
+
+ const QString localhost = u"localhost"_s;
+ const QUrl localhostCallback = u"http://localhost/"_s;
+
+ const QString ipv4literal = u"127.0.0.1"_s;
+ const QUrl ipv4literalCallback = u"http://127.0.0.1/"_s;
+
+ const QString ipv6literal = u"::1"_s;
+ const QUrl ipv6literalCallback = u"http://[::1]/"_s;
+
+ QTest::newRow("default")
+ << QHostAddress() << ipv4literalCallback;
+ QTest::newRow("LocalHost")
+ << QHostAddress(QHostAddress::SpecialAddress::LocalHost) << ipv4literalCallback;
+ QTest::newRow("127.0.0.1")
+ << QHostAddress(ipv4literal) << ipv4literalCallback;
+ QTest::newRow("LocalHostIPv6")
+ << QHostAddress(QHostAddress::SpecialAddress::LocalHostIPv6) << ipv6literalCallback;
+ QTest::newRow("::1")
+ << QHostAddress(ipv6literal) << ipv6literalCallback;
+ QTest::newRow("AnyIPv4")
+ << QHostAddress(QHostAddress::SpecialAddress::AnyIPv4) << localhostCallback;
+ QTest::newRow("0.0.0.0")
+ << QHostAddress("0.0.0.0") << localhostCallback;
+ QTest::newRow("AnyIPv6")
+ << QHostAddress(QHostAddress::SpecialAddress::AnyIPv6) << localhostCallback;
+ QTest::newRow("::")
+ << QHostAddress("::") << localhostCallback;
+}
+
+void tst_QOAuthHttpServerReplyHandler::callbackHost()
+{
+ QFETCH(const QHostAddress, hostAddress);
+ QFETCH(QUrl, expectedCallback);
+
+ QOAuthHttpServerReplyHandler replyHandler(hostAddress, 0); // 0 for any port
+
+ QVERIFY(replyHandler.isListening());
+ expectedCallback.setPort(replyHandler.port());
+ QCOMPARE(QUrl(replyHandler.callback()), expectedCallback);
+}
QTEST_MAIN(tst_QOAuthHttpServerReplyHandler)
#include "tst_oauthhttpserverreplyhandler.moc"