summaryrefslogtreecommitdiffstats
path: root/src/oauth/qoauth2authorizationcodeflow.cpp
diff options
context:
space:
mode:
authorJuha Vuolle <juha.vuolle@qt.io>2023-04-23 20:08:32 +0300
committerJuha Vuolle <juha.vuolle@qt.io>2023-05-10 09:28:22 +0300
commit32f29d3e227da206f262efa055d1cac895855a98 (patch)
tree95e6154205e37fd133e6229c2930e1f73f272783 /src/oauth/qoauth2authorizationcodeflow.cpp
parentc7812a421f3ad250d7d0bf79580296eee697bf25 (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.cpp62
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);
}
/*!