diff options
| author | Juha Vuolle <juha.vuolle@qt.io> | 2023-04-23 20:08:32 +0300 |
|---|---|---|
| committer | Juha Vuolle <juha.vuolle@qt.io> | 2023-05-10 09:28:22 +0300 |
| commit | 32f29d3e227da206f262efa055d1cac895855a98 (patch) | |
| tree | 95e6154205e37fd133e6229c2930e1f73f272783 /src/oauth/qoauth2authorizationcodeflow.cpp | |
| parent | c7812a421f3ad250d7d0bf79580296eee697bf25 (diff) | |
Improve error handling and reporting in OAuth2
The OAuth2 authorization and access token requests can fail for a number
of reasons, both network and authorization server related. These errors
are reported as a log output, leaving the application unaware.
In addition since the refresh token errors were not handled, a failed
refresh attempt left the OAuth2 class in a "refershing token" status
without proper means for application to recover.
This commit harnesses the pre-existing QAbstractOAuth::requestFailed()
signal for reporting these issues. It's used by OAuth1 implementation
for similar purpose.
This consists of:
- Document the requestFailed() signal
- Add new QAbstractOAuthReplyHandler::tokenRequestError() signal,
which reply handlers can emit upon error
- Connect AuthorizationCodeFlow class to that signal and handle it
- Implement error emission in OobReplyHandler, which is used by
the examples (via HTTPReplyHandler)
- Autotests
[ChangeLog][QAbstractOAuth] Add token request error signal and
improve related error handling
Fixes: QTBUG-102279
Fixes: QTBUG-106821
Change-Id: I4dc14aa237d92bd1a2ba830c349cae4121be2e57
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
Reviewed-by: MÃ¥rten Nordheim <marten.nordheim@qt.io>
Diffstat (limited to 'src/oauth/qoauth2authorizationcodeflow.cpp')
| -rw-r--r-- | src/oauth/qoauth2authorizationcodeflow.cpp | 62 |
1 files changed, 50 insertions, 12 deletions
diff --git a/src/oauth/qoauth2authorizationcodeflow.cpp b/src/oauth/qoauth2authorizationcodeflow.cpp index c9e3b00..2a7a7bd 100644 --- a/src/oauth/qoauth2authorizationcodeflow.cpp +++ b/src/oauth/qoauth2authorizationcodeflow.cpp @@ -19,6 +19,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + /*! \class QOAuth2AuthorizationCodeFlow \inmodule QtNetworkAuth @@ -61,7 +63,8 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &d using Key = QAbstractOAuth2Private::OAuth2KeyString; if (status != QAbstractOAuth::Status::NotAuthenticated) { - qCWarning(loggingCategory, "Unexpected call"); + qCWarning(loggingCategory) << "Authorization stage: callback in unexpected status:" + << static_cast<int>(status) << ", ignoring the callback"; return; } @@ -71,23 +74,30 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_handleCallback(const QVariantMap &d const QString code = data.value(Key::code).toString(); const QString receivedState = data.value(Key::state).toString(); if (error.size()) { + // RFC 6749, Section 5.2 Error Response const QString uri = data.value(Key::errorUri).toString(); const QString description = data.value(Key::errorDescription).toString(); - qCWarning(loggingCategory, "AuthenticationError: %s(%s): %s", - qPrintable(error), qPrintable(uri), qPrintable(description)); + qCWarning(loggingCategory, "Authorization stage: AuthenticationError: %s(%s): %s", + qPrintable(error), qPrintable(uri), qPrintable(description)); Q_EMIT q->error(error, description, uri); + // Emit also requestFailed() so that it is a signal for all errors + emit q->requestFailed(QAbstractOAuth::Error::ServerError); return; } + if (code.isEmpty()) { - qCWarning(loggingCategory, "AuthenticationError: Code not received"); + qCWarning(loggingCategory, "Authorization stage: Code not received"); + emit q->requestFailed(QAbstractOAuth::Error::OAuthTokenNotFoundError); return; } if (receivedState.isEmpty()) { - qCWarning(loggingCategory, "State not received"); + qCWarning(loggingCategory, "Authorization stage: State not received"); + emit q->requestFailed(QAbstractOAuth::Error::ServerError); return; } if (state != receivedState) { - qCWarning(loggingCategory, "State mismatch"); + qCWarning(loggingCategory) << "Authorization stage: State mismatch"; + emit q->requestFailed(QAbstractOAuth::Error::ServerError); return; } @@ -105,8 +115,8 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QV using Key = QAbstractOAuth2Private::OAuth2KeyString; if (values.contains(Key::error)) { - const QString error = values.value(Key::error).toString(); - qCWarning(loggingCategory, "Error: %s", qPrintable(error)); + _q_accessTokenRequestFailed(QAbstractOAuth::Error::ServerError, + values.value(Key::error).toString()); return; } @@ -120,7 +130,8 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QV q->setRefreshToken(values.value(Key::refreshToken).toString()); scope = values.value(Key::scope).toString(); if (accessToken.isEmpty()) { - qCWarning(loggingCategory, "Access token not received"); + _q_accessTokenRequestFailed(QAbstractOAuth::Error::OAuthTokenNotFoundError, + "Access token not received"_L1); return; } q->setToken(accessToken); @@ -142,6 +153,23 @@ void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFinished(const QV setStatus(QAbstractOAuth::Status::Granted); } +void QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed(QAbstractOAuth::Error error, + const QString& errorString) +{ + Q_Q(QOAuth2AuthorizationCodeFlow); + qCWarning(loggingCategory) << "Token request failed:" << errorString; + // If we were refreshing, reset status to Granted if we have an access token. + // The access token might still be valid, and even if it wouldn't be, + // refreshing can be attempted again. + if (q->status() == QAbstractOAuth::Status::RefreshingToken) { + if (!q->token().isEmpty()) + setStatus(QAbstractOAuth::Status::Granted); + else + setStatus(QAbstractOAuth::Status::NotAuthenticated); + } + emit q->requestFailed(error); +} + void QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate(QNetworkReply *reply, QAuthenticator *authenticator) { @@ -273,8 +301,12 @@ void QOAuth2AuthorizationCodeFlow::grant() permanent. After a time specified along with the access token when it was obtained, the access token will become invalid. - \b {See also}: - \l {https://tools.ietf.org/html/rfc6749#section-1.5}{Refresh + If refreshing the token fails and an access token exists, the status is + set to QAbstractOAuth::Status::Granted, and to + QAbstractOAuth::Status::NotAuthenticated otherwise. + + \sa QAbstractOAuth::requestFailed() + \sa {https://tools.ietf.org/html/rfc6749#section-1.5}{Refresh Token} */ void QOAuth2AuthorizationCodeFlow::refreshAccessToken() @@ -313,7 +345,7 @@ void QOAuth2AuthorizationCodeFlow::refreshAccessToken() const QString data = query.toString(QUrl::FullyEncoded); d->currentReply = d->networkAccessManager()->post(request, data.toUtf8()); - d->status = Status::RefreshingToken; + setStatus(Status::RefreshingToken); QNetworkReply *reply = d->currentReply.data(); QAbstractOAuthReplyHandler *handler = replyHandler(); @@ -327,6 +359,9 @@ void QOAuth2AuthorizationCodeFlow::refreshAccessToken() &QNetworkAccessManager::authenticationRequired, d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate, Qt::UniqueConnection); + QObjectPrivate::connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::tokenRequestError, + d, &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed, + Qt::UniqueConnection); } /*! @@ -403,6 +438,9 @@ void QOAuth2AuthorizationCodeFlow::requestAccessToken(const QString &code) &QNetworkAccessManager::authenticationRequired, d, &QOAuth2AuthorizationCodeFlowPrivate::_q_authenticate, Qt::UniqueConnection); + QObjectPrivate::connect(d->replyHandler.data(), &QAbstractOAuthReplyHandler::tokenRequestError, + d, &QOAuth2AuthorizationCodeFlowPrivate::_q_accessTokenRequestFailed, + Qt::UniqueConnection); } /*! |
