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"