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

0001 #include <QCryptographicHash>
0002 #include <QNetworkRequest>
0003 #include <QDateTime>
0004 #include <QByteArray>
0005 #include <QDebug>
0006 #include <QDataStream>
0007 #include <QStringList>
0008 #include <algorithm>
0009 
0010 #if QT_VERSION >= 0x050000
0011 #include <QUrlQuery>
0012 #endif
0013 
0014 #if QT_VERSION >= 0x050100
0015 #include <QMessageAuthenticationCode>
0016 #endif
0017 
0018 #if QT_VERSION >= 0x051500
0019 #include <QRandomGenerator>
0020 #endif
0021 
0022 
0023 #include "o1.h"
0024 #include "o2replyserver.h"
0025 #include "o0globals.h"
0026 #include "o0settingsstore.h"
0027 
0028 O1::O1(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) {
0029     setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA256);
0030     manager_ = manager ? manager : new QNetworkAccessManager(this);
0031     qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
0032 
0033     setCallbackUrl(O2_CALLBACK_URL);
0034 }
0035 
0036 QByteArray O1::userAgent() const {
0037     return userAgent_;
0038 }
0039 
0040 void O1::setUserAgent(const QByteArray &v) {
0041     userAgent_ = v;
0042 }
0043 
0044 QUrl O1::requestTokenUrl() {
0045     return requestTokenUrl_;
0046 }
0047 
0048 void O1::setRequestTokenUrl(const QUrl &v) {
0049     requestTokenUrl_ = v;
0050     Q_EMIT requestTokenUrlChanged();
0051 }
0052 
0053 QList<O0RequestParameter> O1::requestParameters() {
0054     return requestParameters_;
0055 }
0056 
0057 void O1::setRequestParameters(const QList<O0RequestParameter> &v) {
0058     requestParameters_ = v;
0059 }
0060 
0061 QString O1::callbackUrl() {
0062     return callbackUrl_;
0063 }
0064 
0065 void O1::setCallbackUrl(const QString &v) {
0066     callbackUrl_ = v;
0067 }
0068 
0069 QUrl O1::authorizeUrl() {
0070     return authorizeUrl_;
0071 }
0072 
0073 void O1::setAuthorizeUrl(const QUrl &value) {
0074     authorizeUrl_ = value;
0075     Q_EMIT authorizeUrlChanged();
0076 }
0077 
0078 QUrl O1::accessTokenUrl() {
0079     return accessTokenUrl_;
0080 }
0081 
0082 void O1::setAccessTokenUrl(const QUrl &value) {
0083     accessTokenUrl_ = value;
0084     Q_EMIT accessTokenUrlChanged();
0085 }
0086 
0087 QString O1::signatureMethod() {
0088     return signatureMethod_;
0089 }
0090 
0091 void O1::setSignatureMethod(const QString &value) {
0092     qDebug() << "O1::setSignatureMethod: " << value;
0093     signatureMethod_ = value;
0094 }
0095 
0096 void O1::unlink() {
0097     qDebug() << "O1::unlink";
0098     setLinked(false);
0099     setToken("");
0100     setTokenSecret("");
0101     setExtraTokens(QVariantMap());
0102     Q_EMIT linkingSucceeded();
0103 }
0104 
0105 #if QT_VERSION < 0x050100
0106 /// Calculate the HMAC variant of SHA1 hash.
0107 /// @author     http://qt-project.org/wiki/HMAC-SHA1.
0108 /// @copyright  Creative Commons Attribution-ShareAlike 2.5 Generic.
0109 static QByteArray hmacSha1(QByteArray key, QByteArray baseString) {
0110     int blockSize = 64;
0111     if (key.length() > blockSize) {
0112         key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
0113     }
0114     QByteArray innerPadding(blockSize, char(0x36));
0115     QByteArray outerPadding(blockSize, char(0x5c));
0116     for (int i = 0; i < key.length(); i++) {
0117         innerPadding[i] = innerPadding[i] ^ key.at(i);
0118         outerPadding[i] = outerPadding[i] ^ key.at(i);
0119     }
0120     QByteArray total = outerPadding;
0121     QByteArray part = innerPadding;
0122     part.append(baseString);
0123     total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1));
0124     QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1);
0125     return hashed.toBase64();
0126 }
0127 #endif
0128 
0129 /// Get HTTP operation name.
0130 static QString getOperationName(QNetworkAccessManager::Operation op) {
0131     switch (op) {
0132     case QNetworkAccessManager::GetOperation: return "GET";
0133     case QNetworkAccessManager::PostOperation: return "POST";
0134     case QNetworkAccessManager::PutOperation: return "PUT";
0135     case QNetworkAccessManager::DeleteOperation: return "DEL";
0136     default: return "";
0137     }
0138 }
0139 
0140 /// Build a concatenated/percent-encoded string from a list of headers.
0141 QByteArray O1::encodeHeaders(const QList<O0RequestParameter> &headers) {
0142     return QUrl::toPercentEncoding(createQueryParameters(headers));
0143 }
0144 
0145 /// Build a base string for signing.
0146 QByteArray O1::getRequestBase(const QList<O0RequestParameter> &oauthParams, const QList<O0RequestParameter> &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) {
0147     QByteArray base;
0148 
0149     // Initialize base string with the operation name (e.g. "GET") and the base URL
0150     base.append(getOperationName(op).toUtf8() + "&");
0151     base.append(QUrl::toPercentEncoding(url.toString(QUrl::RemoveQuery)) + "&");
0152 
0153     // Append a sorted+encoded list of all request parameters to the base string
0154     QList<O0RequestParameter> headers(oauthParams);
0155     headers.append(otherParams);
0156     std::sort(headers.begin(), headers.end());
0157     base.append(encodeHeaders(headers));
0158 
0159     return base;
0160 }
0161 
0162 QByteArray O1::sign(const QList<O0RequestParameter> &oauthParams, const QList<O0RequestParameter> &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) {
0163     QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op);
0164     QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret);
0165 #if QT_VERSION >= 0x050100
0166     return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha256).toBase64();
0167 #else
0168     return hmacSha1(secret, baseString);
0169 #endif
0170 }
0171 
0172 QByteArray O1::buildAuthorizationHeader(const QList<O0RequestParameter> &oauthParams) {
0173     bool first = true;
0174     QByteArray ret("OAuth ");
0175     QList<O0RequestParameter> headers(oauthParams);
0176     std::sort(headers.begin(), headers.end());
0177     foreach (O0RequestParameter h, headers) {
0178         if (first) {
0179             first = false;
0180         } else {
0181             ret.append(",");
0182         }
0183         ret.append(h.name);
0184         ret.append("=\"");
0185         ret.append(QUrl::toPercentEncoding(h.value));
0186         ret.append("\"");
0187     }
0188     return ret;
0189 }
0190 
0191 void O1::decorateRequest(QNetworkRequest &req, const QList<O0RequestParameter> &oauthParams) {
0192     req.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams));
0193     if (!userAgent_.isEmpty()) {
0194 #if QT_VERSION >= 0x050000
0195         req.setHeader(QNetworkRequest::UserAgentHeader, userAgent_);
0196 #else
0197         req.setRawHeader("User-Agent", userAgent_);
0198 #endif
0199     }
0200 }
0201 
0202 QByteArray O1::generateSignature(const QList<O0RequestParameter> headers, const QNetworkRequest &req, const QList<O0RequestParameter> &signingParameters, QNetworkAccessManager::Operation operation) {
0203     QByteArray signature;
0204     if (signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA256) {
0205         signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret());
0206     } else if (signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) {
0207         signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1();
0208     }
0209     return signature;
0210 }
0211 
0212 void O1::link() {
0213     qDebug() << "O1::link";
0214 
0215     // Create the reply server if it doesn't exist
0216     // and we don't use an external web interceptor
0217     if(!useExternalWebInterceptor_) {
0218         if(replyServer() == NULL) {
0219             O2ReplyServer * replyServer = new O2ReplyServer(this);
0220             connect(replyServer, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
0221             setReplyServer(replyServer);
0222         }
0223     }
0224 
0225     if (linked()) {
0226         qDebug() << "O1::link: Linked already";
0227         Q_EMIT linkingSucceeded();
0228         return;
0229     }
0230 
0231     setLinked(false);
0232     setToken("");
0233     setTokenSecret("");
0234     setExtraTokens(QVariantMap());
0235 
0236     if (!useExternalWebInterceptor_) {
0237         // Start reply server
0238         if (!replyServer()->isListening())
0239             replyServer()->listen(QHostAddress::Any, localPort());
0240     }
0241 
0242     // Get any query parameters for the request
0243 #if QT_VERSION >= 0x050000
0244     QUrlQuery requestData;
0245 #else
0246     QUrl requestData = requestTokenUrl();
0247 #endif
0248     O0RequestParameter param("", "");
0249     foreach(param, requestParameters())
0250       requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value)));
0251 
0252     // Get the request url and add parameters
0253 #if QT_VERSION >= 0x050000
0254     QUrl requestUrl = requestTokenUrl();
0255     requestUrl.setQuery(requestData);
0256     // Create request
0257     QNetworkRequest request(requestUrl);
0258 #else
0259     // Create request
0260     QNetworkRequest request(requestData);
0261 #endif
0262 
0263     // Create initial token request
0264     QList<O0RequestParameter> headers;
0265     headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1()));
0266     headers.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1()));
0267     headers.append(O0RequestParameter(O2_OAUTH_NONCE, nonce()));
0268 #if QT_VERSION >= 0x050800
0269     headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1()));
0270 #else
0271     headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1()));
0272 #endif
0273     headers.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0"));
0274     headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1()));
0275     headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, requestParameters(), QNetworkAccessManager::PostOperation)));
0276     qDebug() << "O1:link: Token request headers:";
0277     foreach(param, headers) {
0278         qDebug() << "  " << param.name << "=" << param.value;
0279     }
0280 
0281     // Clear request token
0282     requestToken_.clear();
0283     requestTokenSecret_.clear();
0284 
0285     // Post request
0286     decorateRequest(request, headers);
0287     request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
0288     QNetworkReply *reply = manager_->post(request, QByteArray());
0289 #if QT_VERSION < 0x051500
0290     connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError)));
0291 #else
0292     connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError)));
0293 #endif
0294     connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished()));
0295 }
0296 
0297 void O1::onTokenRequestError(QNetworkReply::NetworkError error) {
0298     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
0299     qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll();
0300     Q_EMIT linkingFailed();
0301 }
0302 
0303 void O1::onTokenRequestFinished() {
0304     qDebug() << "O1::onTokenRequestFinished";
0305     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
0306     qDebug() << QString( "Request: %1" ).arg(reply->request().url().toString());
0307     reply->deleteLater();
0308     if (reply->error() != QNetworkReply::NoError) {
0309         qWarning() << "O1::onTokenRequestFinished: " << reply->errorString();
0310         return;
0311     }
0312 
0313     // Get request token and secret
0314     QByteArray data = reply->readAll();
0315     QMap<QString, QString> response = parseResponse(data);
0316     requestToken_ = response.value(O2_OAUTH_TOKEN, "");
0317     requestTokenSecret_ = response.value(O2_OAUTH_TOKEN_SECRET, "");
0318     setToken(requestToken_);
0319     setTokenSecret(requestTokenSecret_);
0320 
0321     // Checking for "oauth_callback_confirmed" is present and set to true
0322     QString oAuthCbConfirmed = response.value(O2_OAUTH_CALLBACK_CONFIRMED, "false");
0323     if (requestToken_.isEmpty() || requestTokenSecret_.isEmpty() || (oAuthCbConfirmed == "false")) {
0324         qWarning() << "O1::onTokenRequestFinished: No oauth_token, oauth_token_secret or oauth_callback_confirmed in response :" << data;
0325         Q_EMIT linkingFailed();
0326         return;
0327     }
0328 
0329     // Continue authorization flow in the browser
0330     QUrl url(authorizeUrl());
0331 #if QT_VERSION < 0x050000
0332     url.addQueryItem(O2_OAUTH_TOKEN, requestToken_);
0333     url.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1());
0334 #else
0335     QUrlQuery query(url);
0336     query.addQueryItem(O2_OAUTH_TOKEN, requestToken_);
0337     query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(localPort()).toLatin1());
0338     url.setQuery(query);
0339 #endif
0340     Q_EMIT openBrowser(url);
0341 }
0342 
0343 void O1::onVerificationReceived(QMap<QString, QString> params) {
0344     qDebug() << "O1::onVerificationReceived";
0345     Q_EMIT closeBrowser();
0346     verifier_ = params.value(O2_OAUTH_VERFIER, "");
0347     if (params.value(O2_OAUTH_TOKEN) == requestToken_) {
0348         // Exchange request token for access token
0349         exchangeToken();
0350     } else {
0351         qWarning() << "O1::onVerificationReceived: oauth_token missing or doesn't match";
0352         Q_EMIT linkingFailed();
0353     }
0354 }
0355 
0356 void O1::exchangeToken() {
0357     qDebug() << "O1::exchangeToken";
0358 
0359     // Create token exchange request
0360     QNetworkRequest request(accessTokenUrl());
0361     QList<O0RequestParameter> oauthParams;
0362     oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1()));
0363     oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0"));
0364 #if QT_VERSION >= 0x050800
0365     oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1()));
0366 #else
0367     oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1()));
0368 #endif
0369     oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce()));
0370     oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1()));
0371     oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1()));
0372     oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1()));
0373     oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList<O0RequestParameter>(), QNetworkAccessManager::PostOperation)));
0374 
0375     // Post request
0376     decorateRequest(request, oauthParams);
0377     request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
0378     QNetworkReply *reply = manager_->post(request, QByteArray());
0379 #if QT_VERSION < 0x051500
0380     connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError)));
0381 #else
0382     connect(reply, SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError)));
0383 #endif
0384     connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished()));
0385 }
0386 
0387 void O1::onTokenExchangeError(QNetworkReply::NetworkError error) {
0388     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
0389     qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll();
0390     Q_EMIT linkingFailed();
0391 }
0392 
0393 void O1::onTokenExchangeFinished() {
0394     qDebug() << "O1::onTokenExchangeFinished";
0395 
0396     QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
0397     reply->deleteLater();
0398     if (reply->error() != QNetworkReply::NoError) {
0399         qWarning() << "O1::onTokenExchangeFinished: " << reply->errorString();
0400         return;
0401     }
0402 
0403     // Get access token and secret
0404     QByteArray data = reply->readAll();
0405     QMap<QString, QString> response = parseResponse(data);
0406     if (response.contains(O2_OAUTH_TOKEN) && response.contains(O2_OAUTH_TOKEN_SECRET)) {
0407         setToken(response.take(O2_OAUTH_TOKEN));
0408         setTokenSecret(response.take(O2_OAUTH_TOKEN_SECRET));
0409         // Set extra tokens if any
0410         if (!response.isEmpty()) {
0411             QVariantMap extraTokens;
0412             foreach (QString key, response.keys()) {
0413                extraTokens.insert(key, response.value(key));
0414             }
0415             setExtraTokens(extraTokens);
0416         }
0417         setLinked(true);
0418         Q_EMIT linkingSucceeded();
0419     } else {
0420         qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data;
0421         Q_EMIT linkingFailed();
0422     }
0423 }
0424 
0425 QMap<QString, QString> O1::parseResponse(const QByteArray &response) {
0426     QMap<QString, QString> ret;
0427     foreach (QByteArray param, response.split('&')) {
0428         QList<QByteArray> kv = param.split('=');
0429         if (kv.length() == 2) {
0430             ret.insert(QUrl::fromPercentEncoding(kv[0]), QUrl::fromPercentEncoding(kv[1]));
0431         }
0432     }
0433     return ret;
0434 }
0435 
0436 QByteArray O1::nonce() {
0437 #if QT_VERSION >= 0x050800
0438     QString u = QString::number(QDateTime::currentSecsSinceEpoch()).toLatin1();
0439 #else
0440     QString u = QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1();
0441 #endif
0442 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
0443     u.append(QString::number(QRandomGenerator::global()->generate()));
0444 #else
0445     static bool firstTime = true;
0446     if (firstTime) {
0447         firstTime = false;
0448         qsrand(QTime::currentTime().msec());
0449     }
0450     u.append(QString::number(qrand()));
0451 #endif
0452     return u.toLatin1();
0453 }
0454 
0455 #include "moc_o1.cpp"