summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIvan Solovev <ivan.solovev@qt.io>2024-12-16 15:23:27 +0100
committerIvan Solovev <ivan.solovev@qt.io>2024-12-19 09:31:19 +0100
commit2b1e3419c5ef43e964f40dbe79d7a7da0de5aa57 (patch)
tree41b134f7ed12c796cacf20bfb9cddf3f4d4512fc
parent13d2e25da470494e0b8e668b6ef225b8f1c3dbb4 (diff)
Qt 7: add QAbstractOAuth2::refreshTokens() virtual slot
The base class already contains all properties and signals related to access token refreshing. However, we can not add a new virtual slot during Qt 6 lifetime. This patch pre-programs the addition of this slot to Qt 7 and adjusts the refresh token logic to call the new slot directly from the base class. This eliminates the need to manually implement auto-refresh logic in the derived classes. The name of the new slot is different from the pre-existing slots in the derived classes, so this patch also pre-programs the renaming of the relevant slots. The patch also pre-programs documentation changes by providing Qt 7 versions of the relevant docs. Picking this change to 6.9 to minimize the amount of merge conflict resolutions. Task-number: QTBUG-132106 Pick-to: 6.9 Change-Id: I453e9aae096abbfddcb8076f808d4a055850f7e0 Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
-rw-r--r--src/oauth/doc/src/qtnetworkauth-oauth2-overview.qdoc4
-rw-r--r--src/oauth/qabstractoauth2.cpp35
-rw-r--r--src/oauth/qabstractoauth2.h5
-rw-r--r--src/oauth/qoauth2authorizationcodeflow.cpp12
-rw-r--r--src/oauth/qoauth2authorizationcodeflow.h4
-rw-r--r--src/oauth/qoauth2authorizationcodeflow_p.h2
-rw-r--r--src/oauth/qoauth2deviceauthorizationflow.cpp8
-rw-r--r--src/oauth/qoauth2deviceauthorizationflow.h4
-rw-r--r--src/oauth/qoauth2deviceauthorizationflow_p.h2
-rw-r--r--tests/auto/oauth2/tst_oauth2.cpp18
-rw-r--r--tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp26
11 files changed, 104 insertions, 16 deletions
diff --git a/src/oauth/doc/src/qtnetworkauth-oauth2-overview.qdoc b/src/oauth/doc/src/qtnetworkauth-oauth2-overview.qdoc
index e9ca7a3..525e5e3 100644
--- a/src/oauth/doc/src/qtnetworkauth-oauth2-overview.qdoc
+++ b/src/oauth/doc/src/qtnetworkauth-oauth2-overview.qdoc
@@ -273,8 +273,12 @@ are also available when refreshing tokens.
To refresh the tokens after an application startup,
the application needs to persist the refresh token securely,
and set it with \l {QAbstractOAuth2::setRefreshToken()}.
+\if !defined(qt7)
\l {QOAuth2AuthorizationCodeFlow::refreshAccessToken()}
or \l {QOAuth2DeviceAuthorizationFlow::refreshAccessToken()} can
+\else
+\l QAbstractOAuth2::refreshTokens() can
+\endif
then be called to request new tokens.
Since Qt 6.9, applications can also use refresh convenience
diff --git a/src/oauth/qabstractoauth2.cpp b/src/oauth/qabstractoauth2.cpp
index 0e44eb7..694cc14 100644
--- a/src/oauth/qabstractoauth2.cpp
+++ b/src/oauth/qabstractoauth2.cpp
@@ -307,6 +307,12 @@ static constexpr auto FallbackRefreshInterval = 2s;
Emitting this signal requires that the access token has
a valid expiration time.
+\if defined(qt7)
+ \note Starting from Qt 7.0, the \l refreshTokens() slot will be
+ automatically called if this signal is emitted and \l autoRefresh is set
+ to \c true. The developers who implement custom authorization flows need
+ to reimplement the \l refreshTokens() slot in their classes.
+\else
The developers who implement custom authorization flows by deriving from
this class, should connect to this signal also in order to implement the
auto-refresh functionality:
@@ -314,8 +320,12 @@ static constexpr auto FallbackRefreshInterval = 2s;
\snippet src_oauth_replyhandlers.cpp custom-class-def
\dots
\snippet src_oauth_replyhandlers.cpp custom-class-impl
+\endif
\sa refreshThreshold, autoRefresh
+\if defined(qt7)
+ \sa refreshTokens()
+\endif
*/
/*!
@@ -358,10 +368,12 @@ static constexpr auto FallbackRefreshInterval = 2s;
This is useful for applications that require uninterrupted
authorization without user intervention.
+\if !defined(qt7)
\note Due to the implementation details and the binary compatibility
promises, developers who implement custom authorization flows by deriving
from this class should still implement the support for automatic refresh on
their own. See \l accessTokenAboutToExpire() for more details.
+\endif
\sa refreshThreshold
*/
@@ -425,6 +437,23 @@ static constexpr auto FallbackRefreshInterval = 2s;
from the server.
*/
+/*!
+\if defined(qt7)
+ \fn void QAbstractOAuth2::refreshTokens()
+ \since 7.0
+
+ Call this slot in order to initiate the refresh token request.
+
+ This slot is called automatically if \l autoRefresh is set to \c true and
+ \l accessTokenAboutToExpire() signal is emitted.
+
+ The derived classes \e must reimplement this slot and provide a custom
+ logic to generate and send the refresh token request.
+
+ \sa autoRefresh, accessTokenAboutToExpire()
+\endif
+*/
+
QAbstractOAuth2Private::QAbstractOAuth2Private(const QPair<QString, QString> &clientCredentials,
const QUrl &authorizationUrl,
QNetworkAccessManager *manager) :
@@ -499,6 +528,12 @@ void QAbstractOAuth2Private::initializeRefreshHandling()
});
QObject::connect(&refreshTimer, &QChronoTimer::timeout, q,
&QAbstractOAuth2::accessTokenAboutToExpire);
+#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
+ QObject::connect(q, &QAbstractOAuth2::accessTokenAboutToExpire, q, [q] {
+ if (q->autoRefresh() && !q->refreshToken().isEmpty())
+ q->refreshTokens();
+ });
+#endif
}
void QAbstractOAuth2Private::updateRefreshTimer(bool clientSideUpdate)
diff --git a/src/oauth/qabstractoauth2.h b/src/oauth/qabstractoauth2.h
index 93c362f..f7fac3c 100644
--- a/src/oauth/qabstractoauth2.h
+++ b/src/oauth/qabstractoauth2.h
@@ -176,6 +176,11 @@ public:
}
void clearNetworkRequestModifier();
+#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
+public Q_SLOTS:
+ virtual void refreshTokens() = 0;
+#endif
+
Q_SIGNALS:
#if QT_DEPRECATED_SINCE(6, 11)
QT_DEPRECATED_VERSION_X_6_11("Use requestedScope and grantedScope properties instead.")
diff --git a/src/oauth/qoauth2authorizationcodeflow.cpp b/src/oauth/qoauth2authorizationcodeflow.cpp
index b75b1a1..aaf9614 100644
--- a/src/oauth/qoauth2authorizationcodeflow.cpp
+++ b/src/oauth/qoauth2authorizationcodeflow.cpp
@@ -178,6 +178,7 @@ QByteArray QOAuth2AuthorizationCodeFlowPrivate::createPKCEChallenge()
Q_UNREACHABLE_RETURN({});
}
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void QOAuth2AuthorizationCodeFlowPrivate::initializeAutoRefresh()
{
Q_Q(QOAuth2AuthorizationCodeFlow);
@@ -186,6 +187,7 @@ void QOAuth2AuthorizationCodeFlowPrivate::initializeAutoRefresh()
q->refreshAccessToken();
});
}
+#endif
/*!
Constructs a QOAuth2AuthorizationCodeFlow object with parent
@@ -219,8 +221,10 @@ QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &client
manager),
parent)
{
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
Q_D(QOAuth2AuthorizationCodeFlow);
d->initializeAutoRefresh();
+#endif
}
/*!
@@ -237,8 +241,10 @@ QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QUrl &authentic
QString(), manager),
parent)
{
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
Q_D(QOAuth2AuthorizationCodeFlow);
d->initializeAutoRefresh();
+#endif
}
/*!
@@ -257,8 +263,10 @@ QOAuth2AuthorizationCodeFlow::QOAuth2AuthorizationCodeFlow(const QString &client
clientIdentifier, manager),
parent)
{
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
Q_D(QOAuth2AuthorizationCodeFlow);
d->initializeAutoRefresh();
+#endif
}
/*!
@@ -402,7 +410,11 @@ void QOAuth2AuthorizationCodeFlow::grant()
\sa {https://tools.ietf.org/html/rfc6749#section-1.5}{Refresh
Token}
*/
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void QOAuth2AuthorizationCodeFlow::refreshAccessToken()
+#else
+void QOAuth2AuthorizationCodeFlow::refreshTokens()
+#endif
{
Q_D(QOAuth2AuthorizationCodeFlow);
diff --git a/src/oauth/qoauth2authorizationcodeflow.h b/src/oauth/qoauth2authorizationcodeflow.h
index 8fb2f66..adf8873 100644
--- a/src/oauth/qoauth2authorizationcodeflow.h
+++ b/src/oauth/qoauth2authorizationcodeflow.h
@@ -68,7 +68,11 @@ public:
public Q_SLOTS:
void grant() override;
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void refreshAccessToken();
+#else
+ void refreshTokens() override;
+#endif
protected:
QUrl buildAuthenticateUrl(const QMultiMap<QString, QVariant> &parameters = {});
diff --git a/src/oauth/qoauth2authorizationcodeflow_p.h b/src/oauth/qoauth2authorizationcodeflow_p.h
index e80cc82..9caee08 100644
--- a/src/oauth/qoauth2authorizationcodeflow_p.h
+++ b/src/oauth/qoauth2authorizationcodeflow_p.h
@@ -43,7 +43,9 @@ public:
QByteArray createPKCEChallenge();
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void initializeAutoRefresh();
+#endif
QOAuth2AuthorizationCodeFlow::PkceMethod pkceMethod
= QOAuth2AuthorizationCodeFlow::PkceMethod::S256;
diff --git a/src/oauth/qoauth2deviceauthorizationflow.cpp b/src/oauth/qoauth2deviceauthorizationflow.cpp
index 5eb7170..92f4645 100644
--- a/src/oauth/qoauth2deviceauthorizationflow.cpp
+++ b/src/oauth/qoauth2deviceauthorizationflow.cpp
@@ -551,6 +551,7 @@ void QOAuth2DeviceAuthorizationFlowPrivate::handleTokenSuccessResponse(const QJs
stopTokenPolling();
}
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void QOAuth2DeviceAuthorizationFlowPrivate::initializeAutoRefresh()
{
Q_Q(QOAuth2DeviceAuthorizationFlow);
@@ -559,6 +560,7 @@ void QOAuth2DeviceAuthorizationFlowPrivate::initializeAutoRefresh()
q->refreshAccessToken();
});
}
+#endif
/*!
Constructs a QOAuth2DeviceAuthorizationFlow object.
@@ -586,7 +588,9 @@ QOAuth2DeviceAuthorizationFlow::QOAuth2DeviceAuthorizationFlow(QNetworkAccessMan
: QAbstractOAuth2(*new QOAuth2DeviceAuthorizationFlowPrivate(manager), parent)
{
Q_D(QOAuth2DeviceAuthorizationFlow);
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
d->initializeAutoRefresh();
+#endif
d->tokenPollingTimer.setInterval(d->defaultPollingInterval);
d->tokenPollingTimer.setSingleShot(false);
connect(&d->tokenPollingTimer, &QChronoTimer::timeout, this, [d]() {
@@ -726,7 +730,11 @@ void QOAuth2DeviceAuthorizationFlow::grant()
\sa QAbstractOAuth::requestFailed()
*/
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void QOAuth2DeviceAuthorizationFlow::refreshAccessToken()
+#else
+void QOAuth2DeviceAuthorizationFlow::refreshTokens()
+#endif
{
Q_D(QOAuth2DeviceAuthorizationFlow);
if (d->status == Status::RefreshingToken && d->currentTokenReply) {
diff --git a/src/oauth/qoauth2deviceauthorizationflow.h b/src/oauth/qoauth2deviceauthorizationflow.h
index ec2c069..136f35b 100644
--- a/src/oauth/qoauth2deviceauthorizationflow.h
+++ b/src/oauth/qoauth2deviceauthorizationflow.h
@@ -44,7 +44,11 @@ public:
public Q_SLOTS:
void grant() override;
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void refreshAccessToken();
+#else
+ void refreshTokens() override;
+#endif
bool startTokenPolling();
void stopTokenPolling();
diff --git a/src/oauth/qoauth2deviceauthorizationflow_p.h b/src/oauth/qoauth2deviceauthorizationflow_p.h
index 2e93f23..53fb3bd 100644
--- a/src/oauth/qoauth2deviceauthorizationflow_p.h
+++ b/src/oauth/qoauth2deviceauthorizationflow_p.h
@@ -64,7 +64,9 @@ public:
void resetCurrentTokenReply();
void resetCurrentAuthorizationReply();
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
void initializeAutoRefresh();
+#endif
QRestAccessManager *network();
diff --git a/tests/auto/oauth2/tst_oauth2.cpp b/tests/auto/oauth2/tst_oauth2.cpp
index 4e5a339..5aabded 100644
--- a/tests/auto/oauth2/tst_oauth2.cpp
+++ b/tests/auto/oauth2/tst_oauth2.cpp
@@ -13,6 +13,12 @@
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+#define REFRESH_TOKENS(obj) obj.refreshAccessToken()
+#else
+#define REFRESH_TOKENS(obj) obj.refreshTokens()
+#endif
+
class tst_OAuth2 : public QObject
{
Q_OBJECT
@@ -371,7 +377,7 @@ public:
VALUE_SET = QByteArray(VALUE_PREFIX) + "_refresh_token"; \
valueReceivedByTokenServer.clear(); \
STAGE_RECEIVED = QAbstractOAuth::Stage::RequestingTemporaryCredentials; \
- oauth2.refreshAccessToken(); \
+ REFRESH_TOKENS(oauth2); \
QCOMPARE(oauth2.status(), QAbstractOAuth::Status::RefreshingToken); \
QTRY_COMPARE(STAGE_RECEIVED, QAbstractOAuth::Stage::RefreshingAccessToken); \
QTRY_COMPARE(valueReceivedByTokenServer, VALUE_SET); \
@@ -388,7 +394,7 @@ public:
replyHandler.emitCallbackReceived({{"code"_L1, "acode"_L1}, {"state"_L1, "a_state"_L1}}); \
QTRY_COMPARE(oauth2.status(), QAbstractOAuth::Status::Granted); \
QVERIFY(valueReceivedByTokenServer.isEmpty()); \
- oauth2.refreshAccessToken(); \
+ REFRESH_TOKENS(oauth2); \
QCOMPARE(oauth2.status(), QAbstractOAuth::Status::RefreshingToken); \
QTRY_COMPARE(oauth2.status(), QAbstractOAuth::Status::Granted); \
QVERIFY(valueReceivedByTokenServer.isEmpty()); \
@@ -546,7 +552,7 @@ void tst_OAuth2::refreshToken()
oauth2.setReplyHandler(&replyHandler);
oauth2.setRefreshToken(QLatin1String("refresh_token"));
QSignalSpy grantedSpy(&oauth2, &QOAuth2AuthorizationCodeFlow::granted);
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(grantedSpy.size(), 1);
QCOMPARE(oauth2.token(), QLatin1String("token"));
}
@@ -589,7 +595,7 @@ void tst_OAuth2::getAndRefreshToken()
QTRY_COMPARE(grantedSpy.size(), 1);
QCOMPARE(oauth2.token(), QLatin1String("authorization_code"));
grantedSpy.clear();
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(grantedSpy.size(), 1);
QCOMPARE(oauth2.token(), QLatin1String("refresh_token"));
}
@@ -687,7 +693,7 @@ void tst_OAuth2::tokenRequestErrors()
// Successfully refresh access token
clearSpies();
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(statusSpy.size(), 2);
QCOMPARE(statusSpy.takeFirst().at(0).value<QAbstractOAuth::Status>(),
QAbstractOAuth::Status::RefreshingToken);
@@ -700,7 +706,7 @@ void tst_OAuth2::tokenRequestErrors()
clearSpies();
replyHandler.aTokenRequestError = QAbstractOAuth::Error::ServerError;
expectWarning();
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(statusSpy.size(), 2);
QCOMPARE(statusSpy.takeFirst().at(0).value<QAbstractOAuth::Status>(),
QAbstractOAuth::Status::RefreshingToken);
diff --git a/tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp b/tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp
index 43dff8c..2152f5b 100644
--- a/tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp
+++ b/tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp
@@ -23,6 +23,12 @@ using Error = QAbstractOAuth::Error;
using Status = QAbstractOAuth::Status;
using Stage = QAbstractOAuth::Stage;
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+#define REFRESH_TOKENS(obj) obj.refreshAccessToken()
+#else
+#define REFRESH_TOKENS(obj) obj.refreshTokens()
+#endif
+
class tst_OAuth2DeviceFlow : public QObject
{
Q_OBJECT
@@ -219,7 +225,7 @@ do { \
receivedTokenRequests.clear(); \
STAGES_RECEIVED.clear(); \
requestFailedSpy.clear(); \
- oauth2.refreshAccessToken(); \
+ REFRESH_TOKENS(oauth2); \
QVERIFY(requestFailedSpy.isEmpty()); \
QCOMPARE(oauth2.status(), Status::RefreshingToken); \
QTRY_COMPARE(STAGES_RECEIVED.size(), 1); \
@@ -243,7 +249,7 @@ do { \
QVERIFY(receivedTokenRequests.at(0).headers.value("test-header-name"_ba).isEmpty()); \
receivedTokenRequests.clear(); \
requestFailedSpy.clear(); \
- oauth2.refreshAccessToken(); \
+ REFRESH_TOKENS(oauth2); \
QVERIFY(requestFailedSpy.isEmpty()); \
QCOMPARE(oauth2.status(), Status::RefreshingToken); \
QTRY_COMPARE(oauth2.status(), Status::Granted); \
@@ -613,7 +619,7 @@ void tst_OAuth2DeviceFlow::getAndRefreshToken()
statusSpy.clear();
receivedTokenRequests.clear();
requestFailedSpy.clear();
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QVERIFY(requestFailedSpy.isEmpty());
QTRY_COMPARE(statusSpy.size(), 2);
QCOMPARE(receivedTokenRequests.size(), 1);
@@ -658,7 +664,7 @@ void tst_OAuth2DeviceFlow::clientError()
// Refresh token missing for refreshing
requestFailedSpy.clear();
expectWarning("empty refresh token");
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(requestFailedSpy.size(), 1);
QCOMPARE(requestFailedSpy.at(0).at(0).value<Error>(), Error::ClientError);
@@ -666,7 +672,7 @@ void tst_OAuth2DeviceFlow::clientError()
requestFailedSpy.clear();
oauth2.setRefreshToken("a-refresh-token"_L1);
expectWarning("No token URL");
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QTRY_COMPARE(requestFailedSpy.size(), 1);
QCOMPARE(requestFailedSpy.at(0).at(0).value<Error>(), Error::ClientError);
@@ -683,7 +689,7 @@ void tst_OAuth2DeviceFlow::clientError()
oauth2.grant();
QTRY_VERIFY(oauth2.isPolling());
expectWarning("polling in progress");
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QCOMPARE(requestFailedSpy.size(), 1);
QCOMPARE(requestFailedSpy.at(0).at(0).value<Error>(), Error::ClientError);
@@ -699,9 +705,9 @@ void tst_OAuth2DeviceFlow::clientError()
requestFailedSpy.clear();
oauth2.stopTokenPolling();
oauth2.setTokenUrl(authorizationServer->url("tokenEndpoint"_L1));
- oauth2.refreshAccessToken();
- oauth2.refreshAccessToken();
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
+ REFRESH_TOKENS(oauth2);
+ REFRESH_TOKENS(oauth2);
QVERIFY(requestFailedSpy.isEmpty());
}
@@ -979,7 +985,7 @@ void tst_OAuth2DeviceFlow::tokenRequestErrors()
tokenHttpStatus = Responses::OK_200;
clearTestVariables();
expectWarning("token not received");
- oauth2.refreshAccessToken();
+ REFRESH_TOKENS(oauth2);
QCOMPARE(oauth2.status(), QAbstractOAuth2::Status::RefreshingToken);
QTRY_COMPARE(requestFailedSpy.size(), 1);
QCOMPARE(receivedTokenRequests.size(), 1);