File indexing completed on 2025-01-19 03:55:39

0001 #include <QList>
0002 #include <QDebug>
0003 #include <QTcpServer>
0004 #include <QMap>
0005 #include <QDateTime>
0006 #include <QCryptographicHash>
0007 #include <QTimer>
0008 #include <QVariantMap>
0009 #include <QUuid>
0010 
0011 #if QT_VERSION >= 0x050000
0012 #include <QUrlQuery>
0013 #endif
0014 
0015 #if QT_VERSION >= 0x050000
0016 #include <QRegularExpression>
0017 #else
0018 #include <QRegExp>
0019 #endif
0020 
0021 #include "o2.h"
0022 #include "o2pollserver.h"
0023 #include "o2replyserver.h"
0024 #include "o0globals.h"
0025 #include "o0jsonresponse.h"
0026 #include "o0settingsstore.h"
0027 
0028 /// Add query parameters to a query
0029 static void addQueryParametersToUrl(QUrl &url,  QList<QPair<QString, QString> > parameters) {
0030 #if QT_VERSION < 0x050000
0031     url.setQueryItems(parameters);
0032 #else
0033     QUrlQuery query(url);
0034     query.setQueryItems(parameters);
0035     url.setQuery(query);
0036 #endif
0037 }
0038 
0039 // ref: https://tools.ietf.org/html/rfc8628#section-3.2
0040 // Exception: Google sign-in uses "verification_url" instead of "*_uri" - we'll accept both.
0041 static bool hasMandatoryDeviceAuthParams(const QVariantMap& params)
0042 {
0043     if (!params.contains(O2_OAUTH2_DEVICE_CODE))
0044         return false;
0045 
0046     if (!params.contains(O2_OAUTH2_USER_CODE))
0047         return false;
0048 
0049     if (!(params.contains(O2_OAUTH2_VERIFICATION_URI) || params.contains(O2_OAUTH2_VERIFICATION_URL)))
0050         return false;
0051 
0052     if (!params.contains(O2_OAUTH2_EXPIRES_IN))
0053         return false;
0054 
0055     return true;
0056 }
0057 
0058 O2::O2(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) {
0059     manager_ = manager ? manager : new QNetworkAccessManager(this);
0060     grantFlow_ = GrantFlowAuthorizationCode;
0061     localhostPolicy_ = QString(O2_CALLBACK_URL);
0062     qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
0063 }
0064 
0065 O2::GrantFlow O2::grantFlow() {
0066     return grantFlow_;
0067 }
0068 
0069 void O2::setGrantFlow(O2::GrantFlow value) {
0070     grantFlow_ = value;
0071     Q_EMIT grantFlowChanged();
0072 }
0073 
0074 QString O2::username() {
0075     return username_;
0076 }
0077 
0078 void O2::setUsername(const QString &value) {
0079     username_ = value;
0080     Q_EMIT usernameChanged();
0081 }
0082 
0083 QString O2::password() {
0084     return password_;
0085 }
0086 
0087 void O2::setPassword(const QString &value) {
0088     password_ = value;
0089     Q_EMIT passwordChanged();
0090 }
0091 
0092 QString O2::scope() {
0093     return scope_;
0094 }
0095 
0096 void O2::setScope(const QString &value) {
0097     scope_ = value;
0098     Q_EMIT scopeChanged();
0099 }
0100 
0101 QString O2::requestUrl() {
0102     return requestUrl_.toString();
0103 }
0104 
0105 void O2::setRequestUrl(const QString &value) {
0106     requestUrl_ = value;
0107     Q_EMIT requestUrlChanged();
0108 }
0109 
0110 QVariantMap O2::extraRequestParams()
0111 {
0112   return extraReqParams_;
0113 }
0114 
0115 void O2::setExtraRequestParams(const QVariantMap &value)
0116 {
0117   extraReqParams_ = value;
0118   Q_EMIT extraRequestParamsChanged();
0119 }
0120 
0121 QString O2::tokenUrl() {
0122     return tokenUrl_.toString();
0123 }
0124 
0125 void O2::setTokenUrl(const QString &value) {
0126     tokenUrl_= value;
0127     Q_EMIT tokenUrlChanged();
0128 }
0129 
0130 QString O2::refreshTokenUrl() {
0131     return refreshTokenUrl_.toString();
0132 }
0133 
0134 void O2::setRefreshTokenUrl(const QString &value) {
0135     refreshTokenUrl_ = value;
0136     Q_EMIT refreshTokenUrlChanged();
0137 }
0138 
0139 QString O2::grantType()
0140 {
0141     if (!grantType_.isEmpty())
0142         return grantType_;
0143 
0144     switch (grantFlow_) {
0145     case GrantFlowAuthorizationCode:
0146         return O2_OAUTH2_GRANT_TYPE_CODE;
0147     case GrantFlowImplicit:
0148         return O2_OAUTH2_GRANT_TYPE_TOKEN;
0149     case GrantFlowResourceOwnerPasswordCredentials:
0150         return O2_OAUTH2_GRANT_TYPE_PASSWORD;
0151     case GrantFlowDevice:
0152         return O2_OAUTH2_GRANT_TYPE_DEVICE;
0153     }
0154 
0155     return QString();
0156 }
0157 
0158 void O2::setGrantType(const QString &value)
0159 {
0160     grantType_ = value;
0161 }
0162 
0163 void O2::link() {
0164     qDebug() << "O2::link";
0165 
0166     // Create the reply server if it doesn't exist
0167     // and we don't use an external web interceptor
0168     if(!useExternalWebInterceptor_) {
0169         if(replyServer() == NULL) {
0170             O2ReplyServer * replyServer = new O2ReplyServer(this);
0171             connect(replyServer, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
0172             connect(replyServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool)));
0173             setReplyServer(replyServer);
0174         }
0175     }
0176 
0177     if (linked()) {
0178         qDebug() << "O2::link: Linked already";
0179         Q_EMIT linkingSucceeded();
0180         return;
0181     }
0182 
0183     setLinked(false);
0184     setToken("");
0185     setTokenSecret("");
0186     setExtraTokens(QVariantMap());
0187     setRefreshToken(QString());
0188     setExpires(0);
0189 
0190     if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) {
0191 
0192 #if QT_VERSION >= 0x050000
0193         QString uniqueState = QUuid::createUuid().toString().remove(QRegularExpression("([^a-zA-Z0-9]|[-])"));
0194 #else
0195         QString uniqueState = QUuid::createUuid().toString().remove(QRegExp("([^a-zA-Z0-9]|[-])"));
0196 #endif
0197         if (useExternalWebInterceptor_) {
0198             // Save redirect URI, as we have to reuse it when requesting the access token
0199             redirectUri_ = localhostPolicy_.arg(localPort());
0200         } else {
0201             // Start listening to authentication replies
0202             if (!replyServer()->isListening()) {
0203                 if (replyServer()->listen(QHostAddress::Any, localPort_)) {
0204                     qDebug() << "O2::link: Reply server listening on port" << localPort();
0205                 } else {
0206                     qWarning() << "O2::link: Reply server failed to start listening on port" << localPort();
0207                     Q_EMIT linkingFailed();
0208                     return;
0209                 }
0210             }
0211 
0212             // Save redirect URI, as we have to reuse it when requesting the access token
0213             redirectUri_ = localhostPolicy_.arg(replyServer()->serverPort());
0214             replyServer()->setUniqueState(uniqueState);
0215         }
0216 
0217         // Assemble intial authentication URL
0218         QList<QPair<QString, QString> > parameters;
0219         parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE),
0220                                     (grantFlow_ == GrantFlowAuthorizationCode)? QString(O2_OAUTH2_GRANT_TYPE_CODE): QString(O2_OAUTH2_GRANT_TYPE_TOKEN)));
0221         parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_));
0222         if ( !redirectUri_.isEmpty() )
0223             parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_));
0224         if ( !scope_.isEmpty() )
0225             parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_.replace( " ", "+" )));
0226         parameters.append(qMakePair(QString(O2_OAUTH2_STATE), uniqueState));
0227         if ( !apiKey_.isEmpty() )
0228             parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_));
0229         foreach (QString key, extraRequestParams().keys()) {
0230             parameters.append(qMakePair(key, extraRequestParams().value(key).toString()));
0231         }
0232         // Show authentication URL with a web browser
0233         QUrl url(requestUrl_);
0234         addQueryParametersToUrl(url, parameters);
0235         qDebug() << "O2::link: Emit openBrowser" << url.toString();
0236         Q_EMIT openBrowser(url);
0237     } else if (grantFlow_ == GrantFlowResourceOwnerPasswordCredentials) {
0238         QList<O0RequestParameter> parameters;
0239         parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8()));
0240         if ( !clientSecret_.isEmpty() )
0241             parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8()));
0242         parameters.append(O0RequestParameter(O2_OAUTH2_USERNAME, username_.toUtf8()));
0243         parameters.append(O0RequestParameter(O2_OAUTH2_PASSWORD, password_.toUtf8()));
0244         parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_GRANT_TYPE_PASSWORD));
0245         parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8()));
0246         if ( !apiKey_.isEmpty() )
0247             parameters.append(O0RequestParameter(O2_OAUTH2_API_KEY, apiKey_.toUtf8()));
0248         foreach (QString key, extraRequestParams().keys()) {
0249             parameters.append(O0RequestParameter(key.toUtf8(), extraRequestParams().value(key).toByteArray()));
0250         }
0251         QByteArray payload = O0BaseAuth::createQueryParameters(parameters);
0252 
0253         qDebug() << "O2::link: Sending token request for resource owner flow";
0254         QUrl url(tokenUrl_);
0255         QNetworkRequest tokenRequest(url);
0256         tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
0257         QNetworkReply *tokenReply = manager_->post(tokenRequest, payload);
0258 
0259         connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
0260 #if QT_VERSION < 0x051500
0261         connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0262 #else
0263         connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0264 #endif
0265     }
0266     else if (grantFlow_ == GrantFlowDevice) {
0267         QList<O0RequestParameter> parameters;
0268         parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8()));
0269         parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8()));
0270         QByteArray payload = O0BaseAuth::createQueryParameters(parameters);
0271 
0272         QUrl url(requestUrl_);
0273         QNetworkRequest deviceRequest(url);
0274         deviceRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
0275         QNetworkReply *tokenReply = manager_->post(deviceRequest, payload);
0276 
0277         connect(tokenReply, SIGNAL(finished()), this, SLOT(onDeviceAuthReplyFinished()), Qt::QueuedConnection);
0278 #if QT_VERSION < 0x051500
0279         connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0280 #else
0281         connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0282 #endif
0283     }
0284 }
0285 
0286 void O2::unlink() {
0287     qDebug() << "O2::unlink";
0288     setLinked(false);
0289     setToken(QString());
0290     setRefreshToken(QString());
0291     setExpires(0);
0292     setExtraTokens(QVariantMap());
0293     Q_EMIT linkingSucceeded();
0294 }
0295 
0296 void O2::onVerificationReceived(const QMap<QString, QString> response) {
0297     qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()";
0298     Q_EMIT closeBrowser();
0299 
0300     if (response.contains("error")) {
0301         qWarning() << "O2::onVerificationReceived: Verification failed:" << response;
0302         Q_EMIT linkingFailed();
0303         return;
0304     }
0305 
0306     if (grantFlow_ == GrantFlowAuthorizationCode) {
0307         // Save access code
0308         setCode(response.value(QString(O2_OAUTH2_GRANT_TYPE_CODE)));
0309 
0310         // Exchange access code for access/refresh tokens
0311         QString query;
0312         if(!apiKey_.isEmpty())
0313             query = QString("?" + QString(O2_OAUTH2_API_KEY) + "=" + apiKey_);
0314         QNetworkRequest tokenRequest(QUrl(tokenUrl_.toString() + query));
0315         tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
0316         tokenRequest.setRawHeader("Accept", O2_MIME_TYPE_JSON);
0317         QMap<QString, QString> parameters;
0318         parameters.insert(O2_OAUTH2_GRANT_TYPE_CODE, code());
0319         parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
0320         parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
0321         parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_);
0322         parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE);
0323         QByteArray data = buildRequestBody(parameters);
0324 
0325         qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(QString(data));
0326 
0327         QNetworkReply *tokenReply = manager_->post(tokenRequest, data);
0328         timedReplies_.add(tokenReply);
0329         connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
0330 #if QT_VERSION < 0x051500
0331         connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0332 #else
0333         connect(tokenReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0334 #endif
0335     } else if (grantFlow_ == GrantFlowImplicit || grantFlow_ == GrantFlowDevice) {
0336       // Check for mandatory tokens
0337       if (response.contains(O2_OAUTH2_ACCESS_TOKEN)) {
0338           qDebug() << "O2::onVerificationReceived: Access token returned for implicit or device flow";
0339           setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
0340           if (response.contains(O2_OAUTH2_EXPIRES_IN)) {
0341             bool ok = false;
0342             int expiresIn = response.value(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
0343             if (ok) {
0344                 qDebug() << "O2::onVerificationReceived: Token expires in" << expiresIn << "seconds";
0345                 setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn));
0346             }
0347           }
0348           if (response.contains(O2_OAUTH2_REFRESH_TOKEN)) {
0349               setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN));
0350           }
0351           setLinked(true);
0352           Q_EMIT linkingSucceeded();
0353       } else {
0354           qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit or device flow";
0355           Q_EMIT linkingFailed();
0356       }
0357     } else {
0358         setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
0359         setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN));
0360     }
0361 }
0362 
0363 QString O2::code() {
0364     QString key = QString(O2_KEY_CODE).arg(clientId_);
0365     return store_->value(key);
0366 }
0367 
0368 void O2::setCode(const QString &c) {
0369     QString key = QString(O2_KEY_CODE).arg(clientId_);
0370     store_->setValue(key, c);
0371 }
0372 
0373 void O2::onTokenReplyFinished() {
0374     qDebug() << "O2::onTokenReplyFinished";
0375     QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
0376     if (!tokenReply)
0377     {
0378       qDebug() << "O2::onTokenReplyFinished: reply is null";
0379       return;
0380     }
0381     if (tokenReply->error() == QNetworkReply::NoError) {
0382         QByteArray replyData = tokenReply->readAll();
0383 
0384         // Dump replyData
0385         // SENSITIVE DATA in RelWithDebInfo or Debug builds
0386         //qDebug() << "O2::onTokenReplyFinished: replyData\n";
0387         //qDebug() << QString( replyData );
0388 
0389         QVariantMap tokens = parseJsonResponse(replyData);
0390 
0391         // Dump tokens
0392         qDebug() << "O2::onTokenReplyFinished: Tokens returned:\n";
0393         foreach (QString key, tokens.keys()) {
0394             // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
0395             qDebug() << key << ": "<< tokens.value( key ).toString().left( 3 ) << "...";
0396         }
0397 
0398         // Check for mandatory tokens
0399         if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) {
0400             qDebug() << "O2::onTokenReplyFinished: Access token returned";
0401             setToken(tokens.take(O2_OAUTH2_ACCESS_TOKEN).toString());
0402             bool ok = false;
0403             int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
0404             if (ok) {
0405                 qDebug() << "O2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds";
0406                 setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn));
0407             }
0408             setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString());
0409             setExtraTokens(tokens);
0410             timedReplies_.remove(tokenReply);
0411             setLinked(true);
0412             Q_EMIT linkingSucceeded();
0413         } else {
0414             qWarning() << "O2::onTokenReplyFinished: Access token missing from response";
0415             Q_EMIT linkingFailed();
0416         }
0417     }
0418     tokenReply->deleteLater();
0419 }
0420 
0421 void O2::onTokenReplyError(QNetworkReply::NetworkError error) {
0422     QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
0423     if (!tokenReply)
0424     {
0425       qDebug() << "O2::onTokenReplyError: reply is null";
0426     } else {
0427       qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString();
0428       qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll();
0429       timedReplies_.remove(tokenReply);
0430     }
0431     
0432     setToken(QString());
0433     setRefreshToken(QString());
0434     Q_EMIT linkingFailed();
0435 }
0436 
0437 QByteArray O2::buildRequestBody(const QMap<QString, QString> &parameters) {
0438     QByteArray body;
0439     bool first = true;
0440     foreach (QString key, parameters.keys()) {
0441         if (first) {
0442             first = false;
0443         } else {
0444             body.append("&");
0445         }
0446         QString value = parameters.value(key);
0447         body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value));
0448     }
0449     return body;
0450 }
0451 
0452 int O2::expires() {
0453     QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
0454     return store_->value(key).toInt();
0455 }
0456 
0457 void O2::setExpires(int v) {
0458     QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
0459     store_->setValue(key, QString::number(v));
0460 }
0461 
0462 void O2::startPollServer(const QVariantMap &params)
0463 {
0464     bool ok = false;
0465     int expiresIn = params[O2_OAUTH2_EXPIRES_IN].toInt(&ok);
0466     if (!ok) {
0467         qWarning() << "O2::startPollServer: No expired_in parameter";
0468         Q_EMIT linkingFailed();
0469         return;
0470     }
0471 
0472     qDebug() << "O2::startPollServer: device_ and user_code expires in" << expiresIn << "seconds";
0473 
0474     QUrl url(tokenUrl_);
0475     QNetworkRequest authRequest(url);
0476     authRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
0477 
0478     const QString deviceCode = params[O2_OAUTH2_DEVICE_CODE].toString();
0479     const QString grantType = grantType_.isEmpty() ? O2_OAUTH2_GRANT_TYPE_DEVICE : grantType_;
0480 
0481     QList<O0RequestParameter> parameters;
0482     parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8()));
0483     if ( !clientSecret_.isEmpty() )
0484         parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8()));
0485     parameters.append(O0RequestParameter(O2_OAUTH2_CODE, deviceCode.toUtf8()));
0486     parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, grantType.toUtf8()));
0487     QByteArray payload = O0BaseAuth::createQueryParameters(parameters);
0488 
0489     O2PollServer * pollServer = new O2PollServer(manager_, authRequest, payload, expiresIn, this);
0490     if (params.contains(O2_OAUTH2_INTERVAL)) {
0491         int interval = params[O2_OAUTH2_INTERVAL].toInt(&ok);
0492         if (ok)
0493             pollServer->setInterval(interval);
0494     }
0495     connect(pollServer, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
0496     connect(pollServer, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool)));
0497     setPollServer(pollServer);
0498     pollServer->startPolling();
0499 }
0500 
0501 QString O2::refreshToken() {
0502     QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
0503     return store_->value(key);
0504 }
0505 
0506 void O2::setRefreshToken(const QString &v) {
0507     qDebug() << "O2::setRefreshToken" << v.left(4) << "...";
0508     QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
0509     store_->setValue(key, v);
0510 }
0511 
0512 void O2::refresh() {
0513     qDebug() << "O2::refresh: Token: ..." << refreshToken().right(7);
0514 
0515     if (refreshToken().isEmpty()) {
0516         qWarning() << "O2::refresh: No refresh token";
0517         onRefreshError(QNetworkReply::AuthenticationRequiredError);
0518         return;
0519     }
0520     if (refreshTokenUrl_.isEmpty()) {
0521         qWarning() << "O2::refresh: Refresh token URL not set";
0522         onRefreshError(QNetworkReply::AuthenticationRequiredError);
0523         return;
0524     }
0525 
0526     QNetworkRequest refreshRequest(refreshTokenUrl_);
0527     refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
0528     QMap<QString, QString> parameters;
0529     parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
0530     parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
0531     parameters.insert(O2_OAUTH2_REFRESH_TOKEN, refreshToken());
0532     parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_REFRESH_TOKEN);
0533 
0534     QByteArray data = buildRequestBody(parameters);
0535     QNetworkReply *refreshReply = manager_->post(refreshRequest, data);
0536     timedReplies_.add(refreshReply);
0537     connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection);
0538 #if QT_VERSION < 0x051500
0539     connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0540 #else
0541     connect(refreshReply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
0542 #endif
0543 }
0544 
0545 void O2::onRefreshFinished() {
0546     QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
0547 
0548     if (refreshReply->error() == QNetworkReply::NoError) {
0549         QByteArray reply = refreshReply->readAll();
0550         QVariantMap tokens = parseJsonResponse(reply);
0551         setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString());
0552         setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt()));
0553         QString refreshToken = tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString();
0554         if(!refreshToken.isEmpty()) {
0555             setRefreshToken(refreshToken);
0556         }
0557         else {
0558             qDebug() << "No new refresh token. Keep the old one.";
0559         }
0560         timedReplies_.remove(refreshReply);
0561         setLinked(true);
0562         Q_EMIT linkingSucceeded();
0563         Q_EMIT refreshFinished(QNetworkReply::NoError);
0564         qDebug() << " New token expires in" << expires() << "seconds";
0565     } else {
0566         qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString();
0567     }
0568     refreshReply->deleteLater();
0569 }
0570 
0571 void O2::onRefreshError(QNetworkReply::NetworkError error) {
0572     QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
0573     qWarning() << "O2::onRefreshError: " << error;
0574     unlink();
0575     timedReplies_.remove(refreshReply);
0576     Q_EMIT refreshFinished(error);
0577 }
0578 
0579 void O2::onDeviceAuthReplyFinished()
0580 {
0581     qDebug() << "O2::onDeviceAuthReplyFinished";
0582     QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
0583     if (!tokenReply)
0584     {
0585       qDebug() << "O2::onDeviceAuthReplyFinished: reply is null";
0586       return;
0587     }
0588     if (tokenReply->error() == QNetworkReply::NoError) {
0589         QByteArray replyData = tokenReply->readAll();
0590 
0591         // Dump replyData
0592         // SENSITIVE DATA in RelWithDebInfo or Debug builds
0593         //qDebug() << "O2::onDeviceAuthReplyFinished: replyData\n";
0594         //qDebug() << QString( replyData );
0595 
0596         QVariantMap params = parseJsonResponse(replyData);
0597 
0598         // Dump tokens
0599         qDebug() << "O2::onDeviceAuthReplyFinished: Tokens returned:\n";
0600         foreach (QString key, params.keys()) {
0601             // SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
0602             qDebug() << key << ": "<< params.value( key ).toString().left( 3 ) << "...";
0603         }
0604 
0605         // Check for mandatory parameters
0606         if (hasMandatoryDeviceAuthParams(params)) {
0607             qDebug() << "O2::onDeviceAuthReplyFinished: Device auth request response";
0608 
0609             const QString userCode = params.take(O2_OAUTH2_USER_CODE).toString();
0610             QUrl uri = params.take(O2_OAUTH2_VERIFICATION_URI).toUrl();
0611             if (uri.isEmpty())
0612                 uri = params.take(O2_OAUTH2_VERIFICATION_URL).toUrl();
0613 
0614             if (params.contains(O2_OAUTH2_VERIFICATION_URI_COMPLETE))
0615                 Q_EMIT openBrowser(params.take(O2_OAUTH2_VERIFICATION_URI_COMPLETE).toUrl());
0616 
0617             Q_EMIT showVerificationUriAndCode(uri, userCode);
0618 
0619             startPollServer(params);
0620         } else {
0621             qWarning() << "O2::onDeviceAuthReplyFinished: Mandatory parameters missing from response";
0622             Q_EMIT linkingFailed();
0623         }
0624     }
0625     tokenReply->deleteLater();
0626 }
0627 
0628 void O2::serverHasClosed(bool paramsfound)
0629 {
0630     if ( !paramsfound ) {
0631         // server has probably timed out after receiving first response
0632         Q_EMIT linkingFailed();
0633     }
0634     // poll server is not re-used for later auth requests
0635     setPollServer(NULL);
0636 }
0637 
0638 QString O2::localhostPolicy() const {
0639     return localhostPolicy_;
0640 }
0641 
0642 void O2::setLocalhostPolicy(const QString &value) {
0643     localhostPolicy_ = value;
0644 }
0645 
0646 QString O2::apiKey() {
0647     return apiKey_;
0648 }
0649 
0650 void O2::setApiKey(const QString &value) {
0651     apiKey_ = value;
0652 }
0653 
0654 bool O2::ignoreSslErrors() {
0655     return timedReplies_.ignoreSslErrors();
0656 }
0657 
0658 void O2::setIgnoreSslErrors(bool ignoreSslErrors) {
0659     timedReplies_.setIgnoreSslErrors(ignoreSslErrors);
0660 }
0661 
0662 #include "moc_o2.cpp"