diff options
| author | Ivan Solovev <ivan.solovev@qt.io> | 2024-12-16 15:23:27 +0100 |
|---|---|---|
| committer | Ivan Solovev <ivan.solovev@qt.io> | 2024-12-19 09:31:19 +0100 |
| commit | 2b1e3419c5ef43e964f40dbe79d7a7da0de5aa57 (patch) | |
| tree | 41b134f7ed12c796cacf20bfb9cddf3f4d4512fc | |
| parent | 13d2e25da470494e0b8e668b6ef225b8f1c3dbb4 (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.qdoc | 4 | ||||
| -rw-r--r-- | src/oauth/qabstractoauth2.cpp | 35 | ||||
| -rw-r--r-- | src/oauth/qabstractoauth2.h | 5 | ||||
| -rw-r--r-- | src/oauth/qoauth2authorizationcodeflow.cpp | 12 | ||||
| -rw-r--r-- | src/oauth/qoauth2authorizationcodeflow.h | 4 | ||||
| -rw-r--r-- | src/oauth/qoauth2authorizationcodeflow_p.h | 2 | ||||
| -rw-r--r-- | src/oauth/qoauth2deviceauthorizationflow.cpp | 8 | ||||
| -rw-r--r-- | src/oauth/qoauth2deviceauthorizationflow.h | 4 | ||||
| -rw-r--r-- | src/oauth/qoauth2deviceauthorizationflow_p.h | 2 | ||||
| -rw-r--r-- | tests/auto/oauth2/tst_oauth2.cpp | 18 | ||||
| -rw-r--r-- | tests/auto/oauth2deviceflow/tst_oauth2deviceflow.cpp | 26 |
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> ¶meters = {}); 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); |
