diff options
author | Petr Mrázek <peterix@gmail.com> | 2021-11-28 18:42:01 +0100 |
---|---|---|
committer | Petr Mrázek <peterix@gmail.com> | 2021-11-28 18:42:01 +0100 |
commit | 285188ea532d0e4d1b2b798f1ab13e35202dca0d (patch) | |
tree | 60881ae834cb96eee61ab78e9845b58c8c6066db /libraries/katabasis/src | |
parent | 0e31f77468a8bf391ef2b31124f7882b7d907881 (diff) | |
download | PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.tar.gz PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.tar.bz2 PrismLauncher-285188ea532d0e4d1b2b798f1ab13e35202dca0d.zip |
GH-4071 handle network errors when logging in with MSA as 'soft'
This makes the tokens not expire when such errors happen.
Only applies to MSA, not the XBox and Mojang steps afterwards.
Further testing and improvements are still needed.
Diffstat (limited to 'libraries/katabasis/src')
-rw-r--r-- | libraries/katabasis/src/DeviceFlow.cpp | 450 | ||||
-rw-r--r-- | libraries/katabasis/src/OAuth2.cpp | 672 | ||||
-rw-r--r-- | libraries/katabasis/src/Reply.cpp | 17 | ||||
-rwxr-xr-x | libraries/katabasis/src/ReplyServer.cpp | 182 |
4 files changed, 460 insertions, 861 deletions
diff --git a/libraries/katabasis/src/DeviceFlow.cpp b/libraries/katabasis/src/DeviceFlow.cpp new file mode 100644 index 00000000..5efd5e7b --- /dev/null +++ b/libraries/katabasis/src/DeviceFlow.cpp @@ -0,0 +1,450 @@ +#include <QList> +#include <QPair> +#include <QDebug> +#include <QTcpServer> +#include <QMap> +#include <QNetworkRequest> +#include <QNetworkReply> +#include <QNetworkAccessManager> +#include <QDateTime> +#include <QCryptographicHash> +#include <QTimer> +#include <QVariantMap> +#include <QUuid> +#include <QDataStream> + +#include <QUrlQuery> + +#include "katabasis/DeviceFlow.h" +#include "katabasis/PollServer.h" +#include "katabasis/Globals.h" + +#include "JsonResponse.h" + +namespace { +// ref: https://tools.ietf.org/html/rfc8628#section-3.2 +// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. +bool hasMandatoryDeviceAuthParams(const QVariantMap& params) +{ + if (!params.contains(Katabasis::OAUTH2_DEVICE_CODE)) + return false; + + if (!params.contains(Katabasis::OAUTH2_USER_CODE)) + return false; + + if (!(params.contains(Katabasis::OAUTH2_VERIFICATION_URI) || params.contains(Katabasis::OAUTH2_VERIFICATION_URL))) + return false; + + if (!params.contains(Katabasis::OAUTH2_EXPIRES_IN)) + return false; + + return true; +} + +QByteArray createQueryParameters(const QList<Katabasis::RequestParameter> ¶meters) { + QByteArray ret; + bool first = true; + for( auto & h: parameters) { + if (first) { + first = false; + } else { + ret.append("&"); + } + ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); + } + return ret; +} +} + +namespace Katabasis { + +DeviceFlow::DeviceFlow(Options & opts, Token & token, QObject *parent, QNetworkAccessManager *manager) : QObject(parent), token_(token) { + manager_ = manager ? manager : new QNetworkAccessManager(this); + qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); + options_ = opts; +} + +bool DeviceFlow::linked() { + return token_.validity != Validity::None; +} +void DeviceFlow::setLinked(bool v) { + qDebug() << "DeviceFlow::setLinked:" << (v? "true": "false"); + token_.validity = v ? Validity::Certain : Validity::None; +} + +void DeviceFlow::updateActivity(Activity activity) +{ + if(activity_ == activity) { + return; + } + + activity_ = activity; + switch(activity) { + case Katabasis::Activity::Idle: + case Katabasis::Activity::LoggingIn: + case Katabasis::Activity::LoggingOut: + case Katabasis::Activity::Refreshing: + // non-terminal states... + break; + case Katabasis::Activity::FailedSoft: + // terminal state, tokens did not change + break; + case Katabasis::Activity::FailedHard: + case Katabasis::Activity::FailedGone: + // terminal state, tokens are invalid + token_ = Token(); + break; + case Katabasis::Activity::Succeeded: + setLinked(true); + break; + } + emit activityChanged(activity_); +} + +QString DeviceFlow::token() { + return token_.token; +} +void DeviceFlow::setToken(const QString &v) { + token_.token = v; +} + +QVariantMap DeviceFlow::extraTokens() { + return token_.extra; +} + +void DeviceFlow::setExtraTokens(QVariantMap extraTokens) { + token_.extra = extraTokens; +} + +void DeviceFlow::setPollServer(PollServer *server) +{ + if (pollServer_) + pollServer_->deleteLater(); + + pollServer_ = server; +} + +PollServer *DeviceFlow::pollServer() const +{ + return pollServer_; +} + +QVariantMap DeviceFlow::extraRequestParams() +{ + return extraReqParams_; +} + +void DeviceFlow::setExtraRequestParams(const QVariantMap &value) +{ + extraReqParams_ = value; +} + +QString DeviceFlow::grantType() +{ + if (!grantType_.isEmpty()) + return grantType_; + + return OAUTH2_GRANT_TYPE_DEVICE; +} + +void DeviceFlow::setGrantType(const QString &value) +{ + grantType_ = value; +} + +// First get the URL and token to display to the user +void DeviceFlow::login() { + qDebug() << "DeviceFlow::link"; + + updateActivity(Activity::LoggingIn); + setLinked(false); + setToken(""); + setExtraTokens(QVariantMap()); + setRefreshToken(QString()); + setExpires(QDateTime()); + + QList<RequestParameter> parameters; + parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); + parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8())); + QByteArray payload = createQueryParameters(parameters); + + QUrl url(options_.authorizationUrl); + QNetworkRequest deviceRequest(url); + deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + QNetworkReply *tokenReply = manager_->post(deviceRequest, payload); + + connect(tokenReply, &QNetworkReply::finished, this, &DeviceFlow::onDeviceAuthReplyFinished, Qt::QueuedConnection); +} + +// Then, once we get them, present them to the user +void DeviceFlow::onDeviceAuthReplyFinished() +{ + qDebug() << "DeviceFlow::onDeviceAuthReplyFinished"; + QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender()); + if (!tokenReply) + { + qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: reply is null"; + return; + } + if (tokenReply->error() == QNetworkReply::NoError) { + QByteArray replyData = tokenReply->readAll(); + + // Dump replyData + // SENSITIVE DATA in RelWithDebInfo or Debug builds + //qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: replyData\n"; + //qDebug() << QString( replyData ); + + QVariantMap params = parseJsonResponse(replyData); + + // Dump tokens + qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Tokens returned:\n"; + foreach (QString key, params.keys()) { + // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first + qDebug() << key << ": "<< params.value( key ).toString(); + } + + // Check for mandatory parameters + if (hasMandatoryDeviceAuthParams(params)) { + qDebug() << "DeviceFlow::onDeviceAuthReplyFinished: Device auth request response"; + + const QString userCode = params.take(OAUTH2_USER_CODE).toString(); + QUrl uri = params.take(OAUTH2_VERIFICATION_URI).toUrl(); + if (uri.isEmpty()) + uri = params.take(OAUTH2_VERIFICATION_URL).toUrl(); + + if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE)) + emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl()); + + bool ok = false; + int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok); + if (!ok) { + qWarning() << "DeviceFlow::startPollServer: No expired_in parameter"; + updateActivity(Activity::FailedHard); + return; + } + + emit showVerificationUriAndCode(uri, userCode, expiresIn); + + startPollServer(params, expiresIn); + } else { + qWarning() << "DeviceFlow::onDeviceAuthReplyFinished: Mandatory parameters missing from response"; + updateActivity(Activity::FailedHard); + } + } + tokenReply->deleteLater(); +} + +// Spin up polling for the user completing the login flow out of band +void DeviceFlow::startPollServer(const QVariantMap ¶ms, int expiresIn) +{ + qDebug() << "DeviceFlow::startPollServer: device_ and user_code expires in" << expiresIn << "seconds"; + + QUrl url(options_.accessTokenUrl); + QNetworkRequest authRequest(url); + authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + const QString deviceCode = params[OAUTH2_DEVICE_CODE].toString(); + const QString grantType = grantType_.isEmpty() ? OAUTH2_GRANT_TYPE_DEVICE : grantType_; + + QList<RequestParameter> parameters; + parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); + if ( !options_.clientSecret.isEmpty() ) { + parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8())); + } + parameters.append(RequestParameter(OAUTH2_CODE, deviceCode.toUtf8())); + parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8())); + QByteArray payload = createQueryParameters(parameters); + + PollServer * pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this); + if (params.contains(OAUTH2_INTERVAL)) { + bool ok = false; + int interval = params[OAUTH2_INTERVAL].toInt(&ok); + if (ok) { + pollServer->setInterval(interval); + } + } + connect(pollServer, &PollServer::verificationReceived, this, &DeviceFlow::onVerificationReceived); + connect(pollServer, &PollServer::serverClosed, this, &DeviceFlow::serverHasClosed); + setPollServer(pollServer); + pollServer->startPolling(); +} + +// Once the user completes the flow, update the internal state and report it to observers +void DeviceFlow::onVerificationReceived(const QMap<QString, QString> response) { + qDebug() << "DeviceFlow::onVerificationReceived: Emitting closeBrowser()"; + emit closeBrowser(); + + if (response.contains("error")) { + qWarning() << "DeviceFlow::onVerificationReceived: Verification failed:" << response; + updateActivity(Activity::FailedHard); + return; + } + + // Check for mandatory tokens + if (response.contains(OAUTH2_ACCESS_TOKEN)) { + qDebug() << "DeviceFlow::onVerificationReceived: Access token returned for implicit or device flow"; + setToken(response.value(OAUTH2_ACCESS_TOKEN)); + if (response.contains(OAUTH2_EXPIRES_IN)) { + bool ok = false; + int expiresIn = response.value(OAUTH2_EXPIRES_IN).toInt(&ok); + if (ok) { + qDebug() << "DeviceFlow::onVerificationReceived: Token expires in" << expiresIn << "seconds"; + setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn)); + } + } + if (response.contains(OAUTH2_REFRESH_TOKEN)) { + setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN)); + } + updateActivity(Activity::Succeeded); + } else { + qWarning() << "DeviceFlow::onVerificationReceived: Access token missing from response for implicit or device flow"; + updateActivity(Activity::FailedHard); + } +} + +// Or if the flow fails or the polling times out, update the internal state with error and report it to observers +void DeviceFlow::serverHasClosed(bool paramsfound) +{ + if ( !paramsfound ) { + // server has probably timed out after receiving first response + updateActivity(Activity::FailedHard); + } + // poll server is not re-used for later auth requests + setPollServer(NULL); +} + +void DeviceFlow::logout() { + qDebug() << "DeviceFlow::unlink"; + updateActivity(Activity::LoggingOut); + // FIXME: implement logout flows... if they exist + token_ = Token(); + updateActivity(Activity::FailedHard); +} + +QDateTime DeviceFlow::expires() { + return token_.notAfter; +} +void DeviceFlow::setExpires(QDateTime v) { + token_.notAfter = v; +} + +QString DeviceFlow::refreshToken() { + return token_.refresh_token; +} + +void DeviceFlow::setRefreshToken(const QString &v) { +#ifndef NDEBUG + qDebug() << "DeviceFlow::setRefreshToken" << v << "..."; +#endif + token_.refresh_token = v; +} + +namespace { +QByteArray buildRequestBody(const QMap<QString, QString> ¶meters) { + QByteArray body; + bool first = true; + foreach (QString key, parameters.keys()) { + if (first) { + first = false; + } else { + body.append("&"); + } + QString value = parameters.value(key); + body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); + } + return body; +} +} + +bool DeviceFlow::refresh() { + qDebug() << "DeviceFlow::refresh: Token: ..." << refreshToken().right(7); + + updateActivity(Activity::Refreshing); + + if (refreshToken().isEmpty()) { + qWarning() << "DeviceFlow::refresh: No refresh token"; + onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr); + return false; + } + if (options_.accessTokenUrl.isEmpty()) { + qWarning() << "DeviceFlow::refresh: Refresh token URL not set"; + onRefreshError(QNetworkReply::AuthenticationRequiredError, nullptr); + return false; + } + + QNetworkRequest refreshRequest(options_.accessTokenUrl); + refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM); + QMap<QString, QString> parameters; + parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier); + if ( !options_.clientSecret.isEmpty() ) { + parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret); + } + parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken()); + parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN); + + QByteArray data = buildRequestBody(parameters); + QNetworkReply *refreshReply = manager_->post(refreshRequest, data); + timedReplies_.add(refreshReply); + connect(refreshReply, &QNetworkReply::finished, this, &DeviceFlow::onRefreshFinished, Qt::QueuedConnection); + return true; +} + +void DeviceFlow::onRefreshFinished() { + QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender()); + + auto networkError = refreshReply->error(); + if (networkError == QNetworkReply::NoError) { + QByteArray reply = refreshReply->readAll(); + QVariantMap tokens = parseJsonResponse(reply); + setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString()); + setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt())); + QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString(); + if(!refreshToken.isEmpty()) { + setRefreshToken(refreshToken); + } + else { + qDebug() << "No new refresh token. Keep the old one."; + } + timedReplies_.remove(refreshReply); + refreshReply->deleteLater(); + updateActivity(Activity::Succeeded); + qDebug() << "New token expires in" << expires() << "seconds"; + } else { + // FIXME: differentiate the error more here + onRefreshError(networkError, refreshReply); + } +} + +void DeviceFlow::onRefreshError(QNetworkReply::NetworkError error, QNetworkReply *refreshReply) { + QString errorString = "No Reply"; + if(refreshReply) { + timedReplies_.remove(refreshReply); + errorString = refreshReply->errorString(); + } + + switch (error) + { + // used for invalid credentials and similar errors. Fall through. + case QNetworkReply::AuthenticationRequiredError: + case QNetworkReply::ContentAccessDenied: + case QNetworkReply::ContentOperationNotPermittedError: + updateActivity(Activity::FailedHard); + break; + case QNetworkReply::ContentGoneError: { + updateActivity(Activity::FailedGone); + break; + } + case QNetworkReply::TimeoutError: + case QNetworkReply::OperationCanceledError: + case QNetworkReply::SslHandshakeFailedError: + default: + updateActivity(Activity::FailedSoft); + return; + } + if(refreshReply) { + refreshReply->deleteLater(); + } + qDebug() << "DeviceFlow::onRefreshFinished: Error" << (int)error << " - " << errorString; +} + +} diff --git a/libraries/katabasis/src/OAuth2.cpp b/libraries/katabasis/src/OAuth2.cpp deleted file mode 100644 index 260aa9c1..00000000 --- a/libraries/katabasis/src/OAuth2.cpp +++ /dev/null @@ -1,672 +0,0 @@ -#include <QList> -#include <QPair> -#include <QDebug> -#include <QTcpServer> -#include <QMap> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QNetworkAccessManager> -#include <QDateTime> -#include <QCryptographicHash> -#include <QTimer> -#include <QVariantMap> -#include <QUuid> -#include <QDataStream> - -#include <QUrlQuery> - -#include "katabasis/OAuth2.h" -#include "katabasis/PollServer.h" -#include "katabasis/ReplyServer.h" -#include "katabasis/Globals.h" - -#include "JsonResponse.h" - -namespace { -// ref: https://tools.ietf.org/html/rfc8628#section-3.2 -// Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both. -bool hasMandatoryDeviceAuthParams(const QVariantMap& params) -{ - if (!params.contains(Katabasis::OAUTH2_DEVICE_CODE)) - return false; - - if (!params.contains(Katabasis::OAUTH2_USER_CODE)) - return false; - - if (!(params.contains(Katabasis::OAUTH2_VERIFICATION_URI) || params.contains(Katabasis::OAUTH2_VERIFICATION_URL))) - return false; - - if (!params.contains(Katabasis::OAUTH2_EXPIRES_IN)) - return false; - - return true; -} - -QByteArray createQueryParameters(const QList<Katabasis::RequestParameter> ¶meters) { - QByteArray ret; - bool first = true; - for( auto & h: parameters) { - if (first) { - first = false; - } else { - ret.append("&"); - } - ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); - } - return ret; -} -} - -namespace Katabasis { - -OAuth2::OAuth2(Options & opts, Token & token, QObject *parent, QNetworkAccessManager *manager) : QObject(parent), token_(token) { - manager_ = manager ? manager : new QNetworkAccessManager(this); - grantFlow_ = GrantFlowAuthorizationCode; - qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError"); - options_ = opts; -} - -bool OAuth2::linked() { - return token_.validity != Validity::None; -} -void OAuth2::setLinked(bool v) { - qDebug() << "OAuth2::setLinked:" << (v? "true": "false"); - token_.validity = v ? Validity::Certain : Validity::None; -} - -QString OAuth2::token() { - return token_.token; -} -void OAuth2::setToken(const QString &v) { - token_.token = v; -} - -QByteArray OAuth2::replyContent() const { - return replyContent_; -} - -void OAuth2::setReplyContent(const QByteArray &value) { - replyContent_ = value; - if (replyServer_) { - replyServer_->setReplyContent(replyContent_); - } -} - -QVariantMap OAuth2::extraTokens() { - return token_.extra; -} - -void OAuth2::setExtraTokens(QVariantMap extraTokens) { - token_.extra = extraTokens; -} - -void OAuth2::setReplyServer(ReplyServer * server) -{ - delete replyServer_; - - replyServer_ = server; - replyServer_->setReplyContent(replyContent_); -} - -ReplyServer * OAuth2::replyServer() const -{ - return replyServer_; -} - -void OAuth2::setPollServer(PollServer *server) -{ - if (pollServer_) - pollServer_->deleteLater(); - - pollServer_ = server; -} - -PollServer *OAuth2::pollServer() const -{ - return pollServer_; -} - -OAuth2::GrantFlow OAuth2::grantFlow() { - return grantFlow_; -} - -void OAuth2::setGrantFlow(OAuth2::GrantFlow value) { - grantFlow_ = value; -} - -QString OAuth2::username() { - return username_; -} - -void OAuth2::setUsername(const QString &value) { - username_ = value; -} - -QString OAuth2::password() { - return password_; -} - -void OAuth2::setPassword(const QString &value) { - password_ = value; -} - -QVariantMap OAuth2::extraRequestParams() -{ - return extraReqParams_; -} - -void OAuth2::setExtraRequestParams(const QVariantMap &value) -{ - extraReqParams_ = value; -} - -QString OAuth2::grantType() -{ - if (!grantType_.isEmpty()) - return grantType_; - - switch (grantFlow_) { - case GrantFlowAuthorizationCode: - return OAUTH2_GRANT_TYPE_CODE; - case GrantFlowImplicit: - return OAUTH2_GRANT_TYPE_TOKEN; - case GrantFlowResourceOwnerPasswordCredentials: - return OAUTH2_GRANT_TYPE_PASSWORD; - case GrantFlowDevice: - return OAUTH2_GRANT_TYPE_DEVICE; - } - - return QString(); -} - -void OAuth2::setGrantType(const QString &value) -{ - grantType_ = value; -} - -void OAuth2::updateActivity(Activity activity) -{ - if(activity_ != activity) { - activity_ = activity; - emit activityChanged(activity_); - } -} - -void OAuth2::link() { - qDebug() << "OAuth2::link"; - - // Create the reply server if it doesn't exist - if(replyServer() == NULL) { - ReplyServer * replyServer = new ReplyServer(this); - connect(replyServer, &ReplyServer::verificationReceived, this, &OAuth2::onVerificationReceived); - connect(replyServer, &ReplyServer::serverClosed, this, &OAuth2::serverHasClosed); - setReplyServer(replyServer); - } - - if (linked()) { - qDebug() << "OAuth2::link: Linked already"; - emit linkingSucceeded(); - return; - } - - setLinked(false); - setToken(""); - setExtraTokens(QVariantMap()); - setRefreshToken(QString()); - setExpires(QDateTime()); - - if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) { - - QString uniqueState = QUuid::createUuid().toString().remove(QRegExp("([^a-zA-Z0-9]|[-])")); - - // FIXME: this should be part of a 'redirection handler' that would get injected into O2 - { - quint16 foundPort = 0; - // Start listening to authentication replies - if (!replyServer()->isListening()) { - auto ports = options_.listenerPorts; - for(auto & port: ports) { - if (replyServer()->listen(QHostAddress::Any, port)) { - foundPort = replyServer()->serverPort(); - qDebug() << "OAuth2::link: Reply server listening on port " << foundPort; - break; - } - } - if(foundPort == 0) { - qWarning() << "OAuth2::link: Reply server failed to start listening on any port out of " << ports; - emit linkingFailed(); - return; - } - } - - // Save redirect URI, as we have to reuse it when requesting the access token - redirectUri_ = options_.redirectionUrl.arg(foundPort); - replyServer()->setUniqueState(uniqueState); - } - - // Assemble intial authentication URL - QUrl url(options_.authorizationUrl); - QUrlQuery query(url); - QList<QPair<QString, QString> > parameters; - query.addQueryItem(OAUTH2_RESPONSE_TYPE, (grantFlow_ == GrantFlowAuthorizationCode)? OAUTH2_GRANT_TYPE_CODE: OAUTH2_GRANT_TYPE_TOKEN); - query.addQueryItem(OAUTH2_CLIENT_ID, options_.clientIdentifier); - query.addQueryItem(OAUTH2_REDIRECT_URI, redirectUri_); - query.addQueryItem(OAUTH2_SCOPE, options_.scope.replace( " ", "+" )); - query.addQueryItem(OAUTH2_STATE, uniqueState); - if (!apiKey_.isEmpty()) { - query.addQueryItem(OAUTH2_API_KEY, apiKey_); - } - for(auto iter = extraReqParams_.begin(); iter != extraReqParams_.end(); iter++) { - query.addQueryItem(iter.key(), iter.value().toString()); - } - url.setQuery(query); - - // Show authentication URL with a web browser - qDebug() << "OAuth2::link: Emit openBrowser" << url.toString(); - emit openBrowser(url); - updateActivity(Activity::LoggingIn); - } else if (grantFlow_ == GrantFlowResourceOwnerPasswordCredentials) { - QList<RequestParameter> parameters; - parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); - if ( !options_.clientSecret.isEmpty() ) { - parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8())); - } - parameters.append(RequestParameter(OAUTH2_USERNAME, username_.toUtf8())); - parameters.append(RequestParameter(OAUTH2_PASSWORD, password_.toUtf8())); - parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, OAUTH2_GRANT_TYPE_PASSWORD)); - parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8())); - if ( !apiKey_.isEmpty() ) - parameters.append(RequestParameter(OAUTH2_API_KEY, apiKey_.toUtf8())); - foreach (QString key, extraRequestParams().keys()) { - parameters.append(RequestParameter(key.toUtf8(), extraRequestParams().value(key).toByteArray())); - } - QByteArray payload = createQueryParameters(parameters); - - qDebug() << "OAuth2::link: Sending token request for resource owner flow"; - QUrl url(options_.accessTokenUrl); - QNetworkRequest tokenRequest(url); - tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QNetworkReply *tokenReply = manager_->post(tokenRequest, payload); - - connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); - connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); - updateActivity(Activity::LoggingIn); - } - else if (grantFlow_ == GrantFlowDevice) { - QList<RequestParameter> parameters; - parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); - parameters.append(RequestParameter(OAUTH2_SCOPE, options_.scope.toUtf8())); - QByteArray payload = createQueryParameters(parameters); - - QUrl url(options_.authorizationUrl); - QNetworkRequest deviceRequest(url); - deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - QNetworkReply *tokenReply = manager_->post(deviceRequest, payload); - - connect(tokenReply, SIGNAL(finished()), this, SLOT(onDeviceAuthReplyFinished()), Qt::QueuedConnection); - connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); - updateActivity(Activity::LoggingIn); - } -} - -void OAuth2::unlink() { - qDebug() << "OAuth2::unlink"; - updateActivity(Activity::LoggingOut); - // FIXME: implement logout flows... if they exist - token_ = Token(); - updateActivity(Activity::Idle); -} - -void OAuth2::onVerificationReceived(const QMap<QString, QString> response) { - qDebug() << "OAuth2::onVerificationReceived: Emitting closeBrowser()"; - emit closeBrowser(); - - if (response.contains("error")) { - qWarning() << "OAuth2::onVerificationReceived: Verification failed:" << response; - emit linkingFailed(); - updateActivity(Activity::Idle); - return; - } - - if (grantFlow_ == GrantFlowAuthorizationCode) { - // NOTE: access code is temporary and should never be saved anywhere! - auto access_code = response.value(QString(OAUTH2_GRANT_TYPE_CODE)); - - // Exchange access code for access/refresh tokens - QString query; - if(!apiKey_.isEmpty()) - query = QString("?" + QString(OAUTH2_API_KEY) + "=" + apiKey_); - QNetworkRequest tokenRequest(QUrl(options_.accessTokenUrl.toString() + query)); - tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM); - tokenRequest.setRawHeader("Accept", MIME_TYPE_JSON); - QMap<QString, QString> parameters; - parameters.insert(OAUTH2_GRANT_TYPE_CODE, access_code); - parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier); - if ( !options_.clientSecret.isEmpty() ) { - parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret); - } - parameters.insert(OAUTH2_REDIRECT_URI, redirectUri_); - parameters.insert(OAUTH2_GRANT_TYPE, AUTHORIZATION_CODE); - QByteArray data = buildRequestBody(parameters); - - qDebug() << QString("OAuth2::onVerificationReceived: Exchange access code data:\n%1").arg(QString(data)); - - QNetworkReply *tokenReply = manager_->post(tokenRequest, data); - timedReplies_.add(tokenReply); - connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection); - connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection); - } else if (grantFlow_ == GrantFlowImplicit || grantFlow_ == GrantFlowDevice) { - // Check for mandatory tokens - if (response.contains(OAUTH2_ACCESS_TOKEN)) { - qDebug() << "OAuth2::onVerificationReceived: Access token returned for implicit or device flow"; - setToken(response.value(OAUTH2_ACCESS_TOKEN)); - if (response.contains(OAUTH2_EXPIRES_IN)) { - bool ok = false; - int expiresIn = response.value(OAUTH2_EXPIRES_IN).toInt(&ok); - if (ok) { - qDebug() << "OAuth2::onVerificationReceived: Token expires in" << expiresIn << "seconds"; - setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn)); - } - } - if (response.contains(OAUTH2_REFRESH_TOKEN)) { - setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN)); - } - setLinked(true); - emit linkingSucceeded(); - } else { - qWarning() << "OAuth2::onVerificationReceived: Access token missing from response for implicit or device flow"; - emit linkingFailed(); - } - updateActivity(Activity::Idle); - } else { - setToken(response.value(OAUTH2_ACCESS_TOKEN)); - setRefreshToken(response.value(OAUTH2_REFRESH_TOKEN)); - updateActivity(Activity::Idle); - } -} - -void OAuth2::onTokenReplyFinished() { - qDebug() << "OAuth2::onTokenReplyFinished"; - QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender()); - if (!tokenReply) - { - qDebug() << "OAuth2::onTokenReplyFinished: reply is null"; - return; - } - if (tokenReply->error() == QNetworkReply::NoError) { - QByteArray replyData = tokenReply->readAll(); - - // Dump replyData - // SENSITIVE DATA in RelWithDebInfo or Debug builds - //qDebug() << "OAuth2::onTokenReplyFinished: replyData\n"; - //qDebug() << QString( replyData ); - - QVariantMap tokens = parseJsonResponse(replyData); - - // Dump tokens - qDebug() << "OAuth2::onTokenReplyFinished: Tokens returned:\n"; - foreach (QString key, tokens.keys()) { - // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first - qDebug() << key << ": "<< tokens.value( key ).toString(); - } - - // Check for mandatory tokens - if (tokens.contains(OAUTH2_ACCESS_TOKEN)) { - qDebug() << "OAuth2::onTokenReplyFinished: Access token returned"; - setToken(tokens.take(OAUTH2_ACCESS_TOKEN).toString()); - bool ok = false; - int expiresIn = tokens.take(OAUTH2_EXPIRES_IN).toInt(&ok); - if (ok) { - qDebug() << "OAuth2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds"; - setExpires(QDateTime::currentDateTimeUtc().addSecs(expiresIn)); - } - setRefreshToken(tokens.take(OAUTH2_REFRESH_TOKEN).toString()); - setExtraTokens(tokens); - timedReplies_.remove(tokenReply); - setLinked(true); - emit linkingSucceeded(); - } else { - qWarning() << "OAuth2::onTokenReplyFinished: Access token missing from response"; - emit linkingFailed(); - } - } - tokenReply->deleteLater(); - updateActivity(Activity::Idle); -} - -void OAuth2::onTokenReplyError(QNetworkReply::NetworkError error) { - QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender()); - if (!tokenReply) - { - qDebug() << "OAuth2::onTokenReplyError: reply is null"; - } else { - qWarning() << "OAuth2::onTokenReplyError: " << error << ": " << tokenReply->errorString(); - qDebug() << "OAuth2::onTokenReplyError: " << tokenReply->readAll(); - timedReplies_.remove(tokenReply); - } - - setToken(QString()); - setRefreshToken(QString()); - emit linkingFailed(); -} - -QByteArray OAuth2::buildRequestBody(const QMap<QString, QString> ¶meters) { - QByteArray body; - bool first = true; - foreach (QString key, parameters.keys()) { - if (first) { - first = false; - } else { - body.append("&"); - } - QString value = parameters.value(key); - body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value)); - } - return body; -} - -QDateTime OAuth2::expires() { - return token_.notAfter; -} -void OAuth2::setExpires(QDateTime v) { - token_.notAfter = v; -} - -void OAuth2::startPollServer(const QVariantMap ¶ms, int expiresIn) -{ - qDebug() << "OAuth2::startPollServer: device_ and user_code expires in" << expiresIn << "seconds"; - - QUrl url(options_.accessTokenUrl); - QNetworkRequest authRequest(url); - authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - const QString deviceCode = params[OAUTH2_DEVICE_CODE].toString(); - const QString grantType = grantType_.isEmpty() ? OAUTH2_GRANT_TYPE_DEVICE : grantType_; - - QList<RequestParameter> parameters; - parameters.append(RequestParameter(OAUTH2_CLIENT_ID, options_.clientIdentifier.toUtf8())); - if ( !options_.clientSecret.isEmpty() ) { - parameters.append(RequestParameter(OAUTH2_CLIENT_SECRET, options_.clientSecret.toUtf8())); - } - parameters.append(RequestParameter(OAUTH2_CODE, deviceCode.toUtf8())); - parameters.append(RequestParameter(OAUTH2_GRANT_TYPE, grantType.toUtf8())); - QByteArray payload = createQueryParameters(parameters); - - PollServer * pollServer = new PollServer(manager_, authRequest, payload, expiresIn, this); - if (params.contains(OAUTH2_INTERVAL)) { - bool ok = false; - int interval = params[OAUTH2_INTERVAL].toInt(&ok); - if (ok) - pollServer->setInterval(interval); - } - connect(pollServer, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>))); - connect(pollServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool))); - setPollServer(pollServer); - pollServer->startPolling(); -} - -QString OAuth2::refreshToken() { - return token_.refresh_token; -} -void OAuth2::setRefreshToken(const QString &v) { -#ifndef NDEBUG - qDebug() << "OAuth2::setRefreshToken" << v << "..."; -#endif - token_.refresh_token = v; -} - -bool OAuth2::refresh() { - qDebug() << "OAuth2::refresh: Token: ..." << refreshToken().right(7); - - if (refreshToken().isEmpty()) { - qWarning() << "OAuth2::refresh: No refresh token"; - onRefreshError(QNetworkReply::AuthenticationRequiredError); - return false; - } - if (options_.accessTokenUrl.isEmpty()) { - qWarning() << "OAuth2::refresh: Refresh token URL not set"; - onRefreshError(QNetworkReply::AuthenticationRequiredError); - return false; - } - - updateActivity(Activity::Refreshing); - - QNetworkRequest refreshRequest(options_.accessTokenUrl); - refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, MIME_TYPE_XFORM); - QMap<QString, QString> parameters; - parameters.insert(OAUTH2_CLIENT_ID, options_.clientIdentifier); - if ( !options_.clientSecret.isEmpty() ) { - parameters.insert(OAUTH2_CLIENT_SECRET, options_.clientSecret); - } - parameters.insert(OAUTH2_REFRESH_TOKEN, refreshToken()); - parameters.insert(OAUTH2_GRANT_TYPE, OAUTH2_REFRESH_TOKEN); - - QByteArray data = buildRequestBody(parameters); - QNetworkReply *refreshReply = manager_->post(refreshRequest, data); - timedReplies_.add(refreshReply); - connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection); - connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection); - return true; -} - -void OAuth2::onRefreshFinished() { - QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender()); - - if (refreshReply->error() == QNetworkReply::NoError) { - QByteArray reply = refreshReply->readAll(); - QVariantMap tokens = parseJsonResponse(reply); - setToken(tokens.value(OAUTH2_ACCESS_TOKEN).toString()); - setExpires(QDateTime::currentDateTimeUtc().addSecs(tokens.value(OAUTH2_EXPIRES_IN).toInt())); - QString refreshToken = tokens.value(OAUTH2_REFRESH_TOKEN).toString(); - if(!refreshToken.isEmpty()) { - setRefreshToken(refreshToken); - } - else { - qDebug() << "No new refresh token. Keep the old one."; - } - timedReplies_.remove(refreshReply); - setLinked(true); - emit linkingSucceeded(); - emit refreshFinished(QNetworkReply::NoError); - qDebug() << "New token expires in" << expires() << "seconds"; - } else { - emit linkingFailed(); - qDebug() << "OAuth2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString(); - } - refreshReply->deleteLater(); - updateActivity(Activity::Idle); -} - -void OAuth2::onRefreshError(QNetworkReply::NetworkError error) { - QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender()); - qWarning() << "OAuth2::onRefreshError: " << error; - unlink(); - timedReplies_.remove(refreshReply); - emit refreshFinished(error); -} - -void OAuth2::onDeviceAuthReplyFinished() -{ - qDebug() << "OAuth2::onDeviceAuthReplyFinished"; - QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender()); - if (!tokenReply) - { - qDebug() << "OAuth2::onDeviceAuthReplyFinished: reply is null"; - return; - } - if (tokenReply->error() == QNetworkReply::NoError) { - QByteArray replyData = tokenReply->readAll(); - - // Dump replyData - // SENSITIVE DATA in RelWithDebInfo or Debug builds - //qDebug() << "OAuth2::onDeviceAuthReplyFinished: replyData\n"; - //qDebug() << QString( replyData ); - - QVariantMap params = parseJsonResponse(replyData); - - // Dump tokens - qDebug() << "OAuth2::onDeviceAuthReplyFinished: Tokens returned:\n"; - foreach (QString key, params.keys()) { - // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first - qDebug() << key << ": "<< params.value( key ).toString(); - } - - // Check for mandatory parameters - if (hasMandatoryDeviceAuthParams(params)) { - qDebug() << "OAuth2::onDeviceAuthReplyFinished: Device auth request response"; - - const QString userCode = params.take(OAUTH2_USER_CODE).toString(); - QUrl uri = params.take(OAUTH2_VERIFICATION_URI).toUrl(); - if (uri.isEmpty()) - uri = params.take(OAUTH2_VERIFICATION_URL).toUrl(); - - if (params.contains(OAUTH2_VERIFICATION_URI_COMPLETE)) - emit openBrowser(params.take(OAUTH2_VERIFICATION_URI_COMPLETE).toUrl()); - - bool ok = false; - int expiresIn = params[OAUTH2_EXPIRES_IN].toInt(&ok); - if (!ok) { - qWarning() << "OAuth2::startPollServer: No expired_in parameter"; - emit linkingFailed(); - return; - } - - emit showVerificationUriAndCode(uri, userCode, expiresIn); - - startPollServer(params, expiresIn); - } else { - qWarning() << "OAuth2::onDeviceAuthReplyFinished: Mandatory parameters missing from response"; - emit linkingFailed(); - updateActivity(Activity::Idle); - } - } - tokenReply->deleteLater(); -} - -void OAuth2::serverHasClosed(bool paramsfound) -{ - if ( !paramsfound ) { - // server has probably timed out after receiving first response - emit linkingFailed(); - } - // poll server is not re-used for later auth requests - setPollServer(NULL); -} - -QString OAuth2::apiKey() { - return apiKey_; -} - -void OAuth2::setApiKey(const QString &value) { - apiKey_ = value; -} - -bool OAuth2::ignoreSslErrors() { - return timedReplies_.ignoreSslErrors(); -} - -void OAuth2::setIgnoreSslErrors(bool ignoreSslErrors) { - timedReplies_.setIgnoreSslErrors(ignoreSslErrors); -} - -} diff --git a/libraries/katabasis/src/Reply.cpp b/libraries/katabasis/src/Reply.cpp index 775b9202..3e27a7e6 100644 --- a/libraries/katabasis/src/Reply.cpp +++ b/libraries/katabasis/src/Reply.cpp @@ -7,25 +7,28 @@ namespace Katabasis { Reply::Reply(QNetworkReply *r, int timeOut, QObject *parent): QTimer(parent), reply(r) { setSingleShot(true); - connect(this, SIGNAL(error(QNetworkReply::NetworkError)), reply, SIGNAL(error(QNetworkReply::NetworkError)), Qt::QueuedConnection); - connect(this, SIGNAL(timeout()), this, SLOT(onTimeOut()), Qt::QueuedConnection); + connect(this, &Reply::timeout, this, &Reply::onTimeOut, Qt::QueuedConnection); start(timeOut); } void Reply::onTimeOut() { - emit error(QNetworkReply::TimeoutError); + timedOut = true; + reply->abort(); } +// ---------------------------- + ReplyList::~ReplyList() { foreach (Reply *timedReply, replies_) { delete timedReply; } } -void ReplyList::add(QNetworkReply *reply) { - if (reply && ignoreSslErrors()) - reply->ignoreSslErrors(); - add(new Reply(reply)); +void ReplyList::add(QNetworkReply *reply, int timeOut) { + if (reply && ignoreSslErrors()) { + reply->ignoreSslErrors(); + } + add(new Reply(reply, timeOut)); } void ReplyList::add(Reply *reply) { diff --git a/libraries/katabasis/src/ReplyServer.cpp b/libraries/katabasis/src/ReplyServer.cpp deleted file mode 100755 index 4598b18a..00000000 --- a/libraries/katabasis/src/ReplyServer.cpp +++ /dev/null @@ -1,182 +0,0 @@ -#include <QTcpServer> -#include <QTcpSocket> -#include <QByteArray> -#include <QString> -#include <QMap> -#include <QPair> -#include <QTimer> -#include <QStringList> -#include <QUrl> -#include <QDebug> -#include <QUrlQuery> - -#include "katabasis/Globals.h" -#include "katabasis/ReplyServer.h" - -namespace Katabasis { - -ReplyServer::ReplyServer(QObject *parent): QTcpServer(parent), - timeout_(15), maxtries_(3), tries_(0) { - qDebug() << "O2ReplyServer: Starting"; - connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection())); - replyContent_ = "<HTML></HTML>"; -} - -void ReplyServer::onIncomingConnection() { - qDebug() << "O2ReplyServer::onIncomingConnection: Receiving..."; - QTcpSocket *socket = nextPendingConnection(); - connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection); - connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater())); - - // Wait for a bit *after* first response, then close server if no useable data has arrived - // Helps with implicit flow, where a URL fragment may need processed by local user-agent and - // sent as secondary query string callback, or additional requests make it through first, - // like for favicons, etc., before such secondary callbacks are fired - QTimer *timer = new QTimer(socket); - timer->setObjectName("timeoutTimer"); - connect(timer, SIGNAL(timeout()), this, SLOT(closeServer())); - timer->setSingleShot(true); - timer->setInterval(timeout() * 1000); - connect(socket, SIGNAL(readyRead()), timer, SLOT(start())); -} - -void ReplyServer::onBytesReady() { - if (!isListening()) { - // server has been closed, stop processing queued connections - return; - } - qDebug() << "O2ReplyServer::onBytesReady: Processing request"; - // NOTE: on first call, the timeout timer is started - QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender()); - if (!socket) { - qWarning() << "O2ReplyServer::onBytesReady: No socket available"; - return; - } - QByteArray reply; - reply.append("HTTP/1.0 200 OK \r\n"); - reply.append("Content-Type: text/html; charset=\"utf-8\"\r\n"); - reply.append(QString("Content-Length: %1\r\n\r\n").arg(replyContent_.size()).toLatin1()); - reply.append(replyContent_); - socket->write(reply); - qDebug() << "O2ReplyServer::onBytesReady: Sent reply"; - - QByteArray data = socket->readAll(); - QMap<QString, QString> queryParams = parseQueryParams(&data); - if (queryParams.isEmpty()) { - if (tries_ < maxtries_ ) { - qDebug() << "O2ReplyServer::onBytesReady: No query params found, waiting for more callbacks"; - ++tries_; - return; - } else { - tries_ = 0; - qWarning() << "O2ReplyServer::onBytesReady: No query params found, maximum callbacks received"; - closeServer(socket, false); - return; - } - } - if (!uniqueState_.isEmpty() && !queryParams.contains(QString(OAUTH2_STATE))) { - qDebug() << "O2ReplyServer::onBytesReady: Malicious or service request"; - closeServer(socket, true); - return; // Malicious or service (e.g. favicon.ico) request - } - qDebug() << "O2ReplyServer::onBytesReady: Query params found, closing server"; - closeServer(socket, true); - emit verificationReceived(queryParams); -} - -QMap<QString, QString> ReplyServer::parseQueryParams(QByteArray *data) { - qDebug() << "O2ReplyServer::parseQueryParams"; - - //qDebug() << QString("O2ReplyServer::parseQueryParams data:\n%1").arg(QString(*data)); - - QString splitGetLine = QString(*data).split("\r\n").first(); - splitGetLine.remove("GET "); - splitGetLine.remove("HTTP/1.1"); - splitGetLine.remove("\r\n"); - splitGetLine.prepend("http://localhost"); - QUrl getTokenUrl(splitGetLine); - - QList< QPair<QString, QString> > tokens; - QUrlQuery query(getTokenUrl); - tokens = query.queryItems(); - QMap<QString, QString> queryParams; - QPair<QString, QString> tokenPair; - foreach (tokenPair, tokens) { - // FIXME: We are decoding key and value again. This helps with Google OAuth, but is it mandated by the standard? - QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed().toLatin1())); - QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1())); - queryParams.insert(key, value); - } - return queryParams; -} - -void ReplyServer::closeServer(QTcpSocket *socket, bool hasparameters) -{ - if (!isListening()) { - return; - } - - qDebug() << "O2ReplyServer::closeServer: Initiating"; - int port = serverPort(); - - if (!socket && sender()) { - QTimer *timer = qobject_cast<QTimer*>(sender()); - if (timer) { - qWarning() << "O2ReplyServer::closeServer: Closing due to timeout"; - timer->stop(); - socket = qobject_cast<QTcpSocket *>(timer->parent()); - timer->deleteLater(); - } - } - if (socket) { - QTimer *timer = socket->findChild<QTimer*>("timeoutTimer"); - if (timer) { - qDebug() << "O2ReplyServer::closeServer: Stopping socket's timeout timer"; - timer->stop(); - } - socket->disconnectFromHost(); - } - close(); - qDebug() << "O2ReplyServer::closeServer: Closed, no longer listening on port" << port; - emit serverClosed(hasparameters); -} - -QByteArray ReplyServer::replyContent() { - return replyContent_; -} - -void ReplyServer::setReplyContent(const QByteArray &value) { - replyContent_ = value; -} - -int ReplyServer::timeout() -{ - return timeout_; -} - -void ReplyServer::setTimeout(int timeout) -{ - timeout_ = timeout; -} - -int ReplyServer::callbackTries() -{ - return maxtries_; -} - -void ReplyServer::setCallbackTries(int maxtries) -{ - maxtries_ = maxtries; -} - -QString ReplyServer::uniqueState() -{ - return uniqueState_; -} - -void ReplyServer::setUniqueState(const QString &state) -{ - uniqueState_ = state; -} - -} |