diff --git a/3rdparty/o2/src/o1.cpp b/3rdparty/o2/src/o1.cpp new file mode 100644 index 0000000000000000000000000000000000000000..168e1ad2bda24f87df9b92c3c01897e7ba6322a4 --- /dev/null +++ b/3rdparty/o2/src/o1.cpp @@ -0,0 +1,447 @@ +#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(); +}