#include #include #include #include #include #include #include #if QT_VERSION >= 0x050000 #include #endif #if QT_VERSION >= 0x050100 #include #endif #include "o1.h" #include "o2replyserver.h" #include "o2globals.h" #include "o2settingsstore.h" #define trace() if (1) qDebug() // #define trace() if (0) qDebug() O1::O1(QObject *parent) : QObject(parent) { setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1); manager_ = new QNetworkAccessManager(this); replyServer_ = new O2ReplyServer(this); localPort_ = 0; qRegisterMetaType("QNetworkReply::NetworkError"); connect(replyServer_, SIGNAL(verificationReceived(QMap)), this, SLOT(onVerificationReceived(QMap))); store_ = new O2SettingsStore(O2_ENCRYPTION_KEY, this); } O1::~O1() { } void O1::setStore(O2AbstractStore *store) { if (!store) { qWarning() << "Store object is null! Using default O2SettingsStore"; return; } // Delete the previously stored object store_->deleteLater(); store_ = store; // re-parent it to this class as we take ownership of it now store_->setParent(this); } bool O1::linked() { return !token().isEmpty(); } QString O1::tokenSecret() { QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); return store_->value(key); } void O1::setTokenSecret(const QString &v) { QString key = QString(O2_KEY_TOKEN_SECRET).arg(clientId_); store_->setValue(key, v); } QString O1::token() { QString key = QString(O2_KEY_TOKEN).arg(clientId_); return store_->value(key); } void O1::setToken(const QString &v) { QString key = QString(O2_KEY_TOKEN).arg(clientId_); store_->setValue(key, v); } QString O1::clientId() { return clientId_; } void O1::setClientId(const QString &value) { clientId_ = value; emit clientIdChanged(); } QString O1::clientSecret() { return clientSecret_; } void O1::setClientSecret(const QString &value) { clientSecret_ = value; emit clientSecretChanged(); } int O1::localPort() { return localPort_; } void O1::setLocalPort(int value) { localPort_ = value; emit localPortChanged(); } QUrl O1::requestTokenUrl() { return requestTokenUrl_; } void O1::setRequestTokenUrl(const QUrl &v) { requestTokenUrl_ = v; emit requestTokenUrlChanged(); } QUrl O1::authorizeUrl() { return authorizeUrl_; } void O1::setAuthorizeUrl(const QUrl &value) { authorizeUrl_ = value; emit authorizeUrlChanged(); } QUrl O1::accessTokenUrl() { return accessTokenUrl_; } void O1::setAccessTokenUrl(const QUrl &value) { accessTokenUrl_ = value; emit accessTokenUrlChanged(); } QString O1::signatureMethod() { return signatureMethod_; } void O1::setSignatureMethod(const QString &value) { signatureMethod_ = value; } QVariantMap O1::extraTokens() const { return extraTokens_; } void O1::setExtraTokens(QVariantMap extraTokens) { extraTokens_ = extraTokens; } void O1::unlink() { trace() << "O1::unlink"; if (linked()) { setToken(""); setTokenSecret(""); emit linkedChanged(); } emit linkingSucceeded(); } #if QT_VERSION < 0x050100 /// Calculate the HMAC variant of SHA1 hash. /// @author http://qt-project.org/wiki/HMAC-SHA1. /// @copyright Creative Commons Attribution-ShareAlike 2.5 Generic. static QByteArray hmacSha1(QByteArray key, QByteArray baseString) { int blockSize = 64; if (key.length() > blockSize) { key = QCryptographicHash::hash(key, QCryptographicHash::Sha1); } QByteArray innerPadding(blockSize, char(0x36)); QByteArray outerPadding(blockSize, char(0x5c)); for (int i = 0; i < key.length(); i++) { innerPadding[i] = innerPadding[i] ^ key.at(i); outerPadding[i] = outerPadding[i] ^ key.at(i); } QByteArray total = outerPadding; QByteArray part = innerPadding; part.append(baseString); total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1)); QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1); return hashed.toBase64(); } #endif /// Get HTTP operation name. static QString getOperationName(QNetworkAccessManager::Operation op) { switch (op) { case QNetworkAccessManager::GetOperation: return "GET"; case QNetworkAccessManager::PostOperation: return "POST"; case QNetworkAccessManager::PutOperation: return "PUT"; case QNetworkAccessManager::DeleteOperation: return "DEL"; default: return ""; } } /// Build a concatenated/percent-encoded string from a list of headers. QByteArray O1::encodeHeaders(const QList &headers) { return QUrl::toPercentEncoding(createQueryParams(headers)); } /// Construct query string from list of headers QByteArray O1::createQueryParams(const QList ¶ms) { QByteArray ret; bool first = true; foreach (O1RequestParameter h, params) { if (first) { first = false; } else { ret.append("&"); } ret.append(QUrl::toPercentEncoding(h.name) + "=" + QUrl::toPercentEncoding(h.value)); } return ret; } /// Build a base string for signing. QByteArray O1::getRequestBase(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op) { QByteArray base; // Initialize base string with the operation name (e.g. "GET") and the base URL base.append(getOperationName(op).toUtf8() + "&"); base.append(QUrl::toPercentEncoding(url.toString(QUrl::RemoveQuery)) + "&"); // Append a sorted+encoded list of all request parameters to the base string QList headers(oauthParams); headers.append(otherParams); qSort(headers); base.append(encodeHeaders(headers)); return base; } QByteArray O1::sign(const QList &oauthParams, const QList &otherParams, const QUrl &url, QNetworkAccessManager::Operation op, const QString &consumerSecret, const QString &tokenSecret) { QByteArray baseString = getRequestBase(oauthParams, otherParams, url, op); QByteArray secret = QUrl::toPercentEncoding(consumerSecret) + "&" + QUrl::toPercentEncoding(tokenSecret); #if QT_VERSION >= 0x050100 return QMessageAuthenticationCode::hash(baseString, secret, QCryptographicHash::Sha1).toBase64(); #else return hmacSha1(secret, baseString); #endif } QByteArray O1::buildAuthorizationHeader(const QList &oauthParams) { bool first = true; QByteArray ret("OAuth "); QList headers(oauthParams); qSort(headers); foreach (O1RequestParameter h, headers) { if (first) { first = false; } else { ret.append(","); } ret.append(h.name); ret.append("=\""); ret.append(QUrl::toPercentEncoding(h.value)); ret.append("\""); } return ret; } QByteArray O1::generateSignature(const QList headers, const QNetworkRequest &req, const QList &signingParameters, QNetworkAccessManager::Operation operation) { QByteArray signature; if(signatureMethod() == O2_SIGNATURE_TYPE_HMAC_SHA1) { signature = sign(headers, signingParameters, req.url(), operation, clientSecret(), tokenSecret()); } else if(signatureMethod() == O2_SIGNATURE_TYPE_PLAINTEXT) { signature = clientSecret().toLatin1() + "&" + tokenSecret().toLatin1(); } return signature; } void O1::link() { trace() << "O1::link"; if (linked()) { trace() << "O1::link: Linked already"; emit linkingSucceeded(); return; } // Start reply server replyServer_->listen(QHostAddress::Any, localPort()); // Create request QNetworkRequest request(requestTokenUrl()); // Create initial token request QList headers; headers.append(O1RequestParameter(O2_OAUTH_CALLBACK, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1())); headers.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); headers.append(O1RequestParameter(O2_OAUTH_NONCE, nonce())); headers.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); headers.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); headers.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); headers.append(O1RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, QList(), QNetworkAccessManager::PostOperation))); // Clear request token requestToken_.clear(); requestTokenSecret_.clear(); // Post request request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(headers)); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenRequestError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(onTokenRequestFinished())); } void O1::onTokenRequestError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll(); emit linkingFailed(); } void O1::onTokenRequestFinished() { trace() << "O1::onTokenRequestFinished"; QNetworkReply *reply = qobject_cast(sender()); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { return; } // Get request token and secret QByteArray data = reply->readAll(); QMap response = parseResponse(data); requestToken_ = response.value(O2_OAUTH_TOKEN, ""); requestTokenSecret_ = response.value(O2_OAUTH_TOKEN_SECRET, ""); setToken(requestToken_); setTokenSecret(requestTokenSecret_); // Checking for "oauth_callback_confirmed" is present and set to true QString oAuthCbConfirmed = response.value(O2_OAUTH_CALLBACK_CONFIRMED, "false"); if (requestToken_.isEmpty() || requestTokenSecret_.isEmpty() || (oAuthCbConfirmed == "false")) { qWarning() << "O1::onTokenRequestFinished: No oauth_token, oauth_token_secret or oauth_callback_confirmed in response :" << data; emit linkingFailed(); return; } // Continue authorization flow in the browser QUrl url(authorizeUrl()); #if QT_VERSION < 0x050000 url.addQueryItem(O2_OAUTH_TOKEN, requestToken_); url.addQueryItem(O2_OAUTH_CALLBACK, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1()); #else QUrlQuery query(url); query.addQueryItem(O2_OAUTH_TOKEN, requestToken_); query.addQueryItem(O2_OAUTH_CALLBACK, QString(O2_CALLBACK_URL).arg(replyServer_->serverPort()).toLatin1()); url.setQuery(query); #endif emit openBrowser(url); } void O1::onVerificationReceived(QMap params) { trace() << "O1::onVerificationReceived"; emit closeBrowser(); verifier_ = params.value(O2_OAUTH_VERFIER, ""); if (params.value(O2_OAUTH_TOKEN) == requestToken_) { // Exchange request token for access token exchangeToken(); } else { qWarning() << "O1::onVerificationReceived: oauth_token missing or doesn't match"; emit linkingFailed(); } } void O1::exchangeToken() { // Create token exchange request QNetworkRequest request(accessTokenUrl()); QList oauthParams; oauthParams.append(O1RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1())); oauthParams.append(O1RequestParameter(O2_OAUTH_VERSION, "1.0")); oauthParams.append(O1RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1())); oauthParams.append(O1RequestParameter(O2_OAUTH_NONCE, nonce())); oauthParams.append(O1RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1())); oauthParams.append(O1RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1())); oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1())); oauthParams.append(O1RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList(), QNetworkAccessManager::PostOperation))); // Post request request.setRawHeader(O2_HTTP_AUTHORIZATION_HEADER, buildAuthorizationHeader(oauthParams)); request.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM); QNetworkReply *reply = manager_->post(request, QByteArray()); connect(reply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenExchangeError(QNetworkReply::NetworkError))); connect(reply, SIGNAL(finished()), this, SLOT(onTokenExchangeFinished())); } void O1::onTokenExchangeError(QNetworkReply::NetworkError error) { QNetworkReply *reply = qobject_cast(sender()); qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll(); emit linkingFailed(); } void O1::onTokenExchangeFinished() { QNetworkReply *reply = qobject_cast(sender()); reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { return; } // Get access token and secret QByteArray data = reply->readAll(); QMap response = parseResponse(data); if (response.contains(O2_OAUTH_TOKEN) && response.contains(O2_OAUTH_TOKEN_SECRET)) { setToken(response.take(O2_OAUTH_TOKEN)); setTokenSecret(response.take(O2_OAUTH_TOKEN_SECRET)); // Set extra tokens if any if (!response.isEmpty()) { QVariantMap extraTokens; foreach (QString key, response.keys()) { extraTokens.insert(key, response.value(key)); } setExtraTokens(extraTokens); } emit linkedChanged(); emit linkingSucceeded(); } else { qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data; emit linkingFailed(); } } QMap O1::parseResponse(const QByteArray &response) { QMap ret; foreach (QByteArray param, response.split('&')) { QList kv = param.split('='); if (kv.length() == 2) { ret.insert(QUrl::fromPercentEncoding(kv[0]), QUrl::fromPercentEncoding(kv[1])); } } return ret; } QByteArray O1::nonce() { static bool firstTime = true; if (firstTime) { firstTime = false; qsrand(QTime::currentTime().msec()); } QString u = QString::number(QDateTime::currentDateTimeUtc().toTime_t()); u.append(QString::number(qrand())); return u.toLatin1(); }