diff --git a/src/libsync/abstractnetworkjob.cpp b/src/libsync/abstractnetworkjob.cpp index d7eaa56dbb..7411e0fdb1 100644 --- a/src/libsync/abstractnetworkjob.cpp +++ b/src/libsync/abstractnetworkjob.cpp @@ -136,7 +136,7 @@ void AbstractNetworkJob::setAuthenticationJob(bool b) } QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUrl &url, -QNetworkRequest req, QIODevice *requestBody) + QNetworkRequest req, QIODevice *requestBody) { auto reply = _account->sendRawRequest(verb, url, req, requestBody); _requestBody = requestBody; diff --git a/src/libsync/creds/oauth.cpp b/src/libsync/creds/oauth.cpp index 08688fc923..1ff5cc3f0b 100644 --- a/src/libsync/creds/oauth.cpp +++ b/src/libsync/creds/oauth.cpp @@ -108,30 +108,19 @@ void OAuth::startAuthentification() QJsonParseError jsonParseError; const QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); QString fieldsError; - const auto getRequiredField = [&json, &fieldsError](const QString &s) { - const auto out = json.constFind(s); - if (out == json.constEnd()) { - fieldsError.append(tr("\tError: Missing field %1\n").arg(s)); - return QJsonValue(); - } - return *out; - }; - const QString accessToken = getRequiredField(QStringLiteral("access_token")).toString(); - const QString refreshToken = getRequiredField(QStringLiteral("refresh_token")).toString(); - const QString tokenType = getRequiredField(QStringLiteral("token_type")).toString().toLower(); + const QString accessToken = getRequiredField(json, QStringLiteral("access_token"), &fieldsError).toString(); + const QString refreshToken = getRequiredField(json, QStringLiteral("refresh_token"), &fieldsError).toString(); + const QString tokenType = getRequiredField(json, QStringLiteral("token_type"), &fieldsError).toString().toLower(); const QString user = json[QStringLiteral("user_id")].toString(); const QUrl messageUrl = json[QStringLiteral("message_url")].toString(); if (reply->error() != QNetworkReply::NoError || jsonParseError.error != QJsonParseError::NoError || !fieldsError.isEmpty() || tokenType != "bearer") { - QString errorReason; - QString errorFromJson = json["error_description"].toString(); - if (errorFromJson.isEmpty()) - errorFromJson = json["error"].toString(); - if (!errorFromJson.isEmpty()) { + QString errorReason = errorFromJson(json); + if (!errorReason.isEmpty()) { errorReason = tr("Error returned from the server: %1") - .arg(errorFromJson.toHtmlEscaped()); + .arg(errorReason.toHtmlEscaped()); } else if (reply->error() != QNetworkReply::NoError) { errorReason = tr("There was an error accessing the 'token' endpoint:
%1") .arg(reply->errorString().toHtmlEscaped()); @@ -182,25 +171,39 @@ void OAuth::startAuthentification() void OAuth::refreshAuthentification(const QString &refreshToken) { - QObject::connect(this, &OAuth::fetchWellKnownFinished, this, [this, &refreshToken]() { + connect(this, &OAuth::fetchWellKnownFinished, this, [this, refreshToken] { auto job = postTokenRequest({ { QStringLiteral("grant_type"), QStringLiteral("refresh_token") }, { QStringLiteral("refresh_token"), refreshToken } }); - QObject::connect(job, &SimpleNetworkJob::finishedSignal, this, [this](QNetworkReply *reply) { - auto jsonData = reply->readAll(); + connect(job, &SimpleNetworkJob::finishedSignal, this, [this, refreshToken](QNetworkReply *reply) { + const auto jsonData = reply->readAll(); + QString accessToken; + QString newRefreshToken = refreshToken; QJsonParseError jsonParseError; + // https://developer.okta.com/docs/reference/api/oidc/#response-properties-2 const QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object(); - const QString accessToken = json["access_token"].toString(); - if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) { - // Invalid or empty JSON: Network error maybe? - qCWarning(lcOauth) << "Error while refreshing the token" << reply->errorString() << jsonData << jsonParseError.errorString(); - } else if (accessToken.isEmpty()) { - // If the json was valid, but the reply did not contain an access token, the token - // is considered expired. (Usually the HTTP reply code is 400) - qCDebug(lcOauth) << "Expired refresh token. Logging out"; - Q_EMIT refreshFinished(QString(), QString()); + const QString errorReason = errorFromJson(json); + if (!errorReason.isEmpty()) { + if (errorReason == QStringLiteral("invalid_grant")) { + newRefreshToken.clear(); + } else { + qCWarning(lcOauth) << tr("Error while refreshing the token: %1").arg(errorReason); + } + } else if (reply->error() != QNetworkReply::NoError) { + qCWarning(lcOauth) << tr("Error while refreshing the token: %1 : %2").arg(reply->errorString(), QString::fromUtf8(jsonData)); } else { - Q_EMIT refreshFinished(accessToken, json["refresh_token"].toString()); + if (jsonParseError.error != QJsonParseError::NoError || json.isEmpty()) { + // Invalid or empty JSON: Network error maybe? + qCWarning(lcOauth) << tr("Error while refreshing the token: %1 : %2").arg(jsonParseError.errorString(), QString::fromUtf8(jsonData)); + } else { + QString error; + accessToken = getRequiredField(json, QStringLiteral("access_token"), &error).toString(); + newRefreshToken = getRequiredField(json, QStringLiteral("refresh_token"), &error).toString(); + if (!error.isEmpty()) { + qCWarning(lcOauth) << tr("The reply from the server did not contain all expected fields\n:%1").arg(error); + } + } } + Q_EMIT refreshFinished(accessToken, newRefreshToken); }); }); fetchWellKnown(); @@ -261,12 +264,33 @@ QByteArray OAuth::generateRandomString(size_t size) const return QByteArray(reinterpret_cast(buffer.data()), static_cast(size * sizeof(quint32))).toBase64(QByteArray::Base64UrlEncoding); } +QVariant OAuth::getRequiredField(const QJsonObject &json, const QString &s, QString *error) +{ + const auto out = json.constFind(s); + if (out == json.constEnd()) { + error->append(tr("\tError: Missing field %1\n").arg(s)); + return QJsonValue(); + } + return *out; +} + +QString OAuth::errorFromJson(const QJsonObject &json) +{ + if (json.isEmpty()) { + return {}; + } + QString errorFromJson = json[QStringLiteral("error_description")].toString(); + if (errorFromJson.isEmpty()) + errorFromJson = json[QStringLiteral("error")].toString(); + return errorFromJson; +} + QUrl OAuth::authorisationLink() const { Q_ASSERT(_server.isListening()); QUrlQuery query; QByteArray code_challenge = QCryptographicHash::hash(_pkceCodeVerifier, QCryptographicHash::Sha256) - .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + .toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); query.setQueryItems({ { QLatin1String("response_type"), QLatin1String("code") }, { QLatin1String("client_id"), Theme::instance()->oauthClientId() }, diff --git a/src/libsync/creds/oauth.h b/src/libsync/creds/oauth.h index 040701e44f..d98386c63a 100644 --- a/src/libsync/creds/oauth.h +++ b/src/libsync/creds/oauth.h @@ -95,6 +95,10 @@ private: QByteArray generateRandomString(size_t size) const; + QVariant getRequiredField(const QJsonObject &json, const QString &s, QString *error); + + QString errorFromJson(const QJsonObject &json); + Account* _account; QTcpServer _server; bool _wellKnownFinished = false; diff --git a/src/libsync/networkjobs.h b/src/libsync/networkjobs.h index 178718e645..d568b0fbf8 100644 --- a/src/libsync/networkjobs.h +++ b/src/libsync/networkjobs.h @@ -420,7 +420,7 @@ public: signals: void finishedSignal(QNetworkReply *reply); private slots: - bool finished() Q_DECL_OVERRIDE; + bool finished() override; }; /**