mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-10 00:13:55 -04:00
390 lines
14 KiB
C++
390 lines
14 KiB
C++
#include <QCryptographicHash>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkAccessManager>
|
|
#include <QDateTime>
|
|
#include <QByteArray>
|
|
#include <QDebug>
|
|
#include <QDataStream>
|
|
#include <QStringList>
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
#include <QUrlQuery>
|
|
#endif
|
|
|
|
#if QT_VERSION >= 0x050100
|
|
#include <QMessageAuthenticationCode>
|
|
#endif
|
|
|
|
#include "o1.h"
|
|
#include "o2replyserver.h"
|
|
#include "o0globals.h"
|
|
#include "o0settingsstore.h"
|
|
|
|
O1::O1(QObject *parent, QNetworkAccessManager *manager): O0BaseAuth(parent) {
|
|
setSignatureMethod(O2_SIGNATURE_TYPE_HMAC_SHA1);
|
|
manager_ = manager ? manager : new QNetworkAccessManager(this);
|
|
replyServer_ = new O2ReplyServer(this);
|
|
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
|
|
connect(replyServer_, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
|
|
setCallbackUrl(O2_CALLBACK_URL);
|
|
}
|
|
|
|
QUrl O1::requestTokenUrl() {
|
|
return requestTokenUrl_;
|
|
}
|
|
|
|
void O1::setRequestTokenUrl(const QUrl &v) {
|
|
requestTokenUrl_ = v;
|
|
Q_EMIT requestTokenUrlChanged();
|
|
}
|
|
|
|
QList<O0RequestParameter> O1::requestParameters() {
|
|
return requestParameters_;
|
|
}
|
|
|
|
void O1::setRequestParameters(const QList<O0RequestParameter> &v) {
|
|
requestParameters_ = v;
|
|
}
|
|
|
|
QString O1::callbackUrl() {
|
|
return callbackUrl_;
|
|
}
|
|
|
|
void O1::setCallbackUrl(const QString &v) {
|
|
callbackUrl_ = v;
|
|
}
|
|
|
|
QUrl O1::authorizeUrl() {
|
|
return authorizeUrl_;
|
|
}
|
|
|
|
void O1::setAuthorizeUrl(const QUrl &value) {
|
|
authorizeUrl_ = value;
|
|
Q_EMIT authorizeUrlChanged();
|
|
}
|
|
|
|
QUrl O1::accessTokenUrl() {
|
|
return accessTokenUrl_;
|
|
}
|
|
|
|
void O1::setAccessTokenUrl(const QUrl &value) {
|
|
accessTokenUrl_ = value;
|
|
Q_EMIT accessTokenUrlChanged();
|
|
}
|
|
|
|
QString O1::signatureMethod() {
|
|
return signatureMethod_;
|
|
}
|
|
|
|
void O1::setSignatureMethod(const QString &value) {
|
|
qDebug() << "O1::setSignatureMethod: " << value;
|
|
signatureMethod_ = value;
|
|
}
|
|
|
|
void O1::unlink() {
|
|
qDebug() << "O1::unlink";
|
|
setLinked(false);
|
|
setToken("");
|
|
setTokenSecret("");
|
|
setExtraTokens(QVariantMap());
|
|
Q_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<O0RequestParameter> &headers) {
|
|
return QUrl::toPercentEncoding(createQueryParameters(headers));
|
|
}
|
|
|
|
/// Build a base string for signing.
|
|
QByteArray O1::getRequestBase(const QList<O0RequestParameter> &oauthParams, const QList<O0RequestParameter> &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<O0RequestParameter> headers(oauthParams);
|
|
headers.append(otherParams);
|
|
qSort(headers);
|
|
base.append(encodeHeaders(headers));
|
|
|
|
return base;
|
|
}
|
|
|
|
QByteArray O1::sign(const QList<O0RequestParameter> &oauthParams, const QList<O0RequestParameter> &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<O0RequestParameter> &oauthParams) {
|
|
bool first = true;
|
|
QByteArray ret("OAuth ");
|
|
QList<O0RequestParameter> headers(oauthParams);
|
|
qSort(headers);
|
|
foreach (O0RequestParameter 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<O0RequestParameter> headers, const QNetworkRequest &req, const QList<O0RequestParameter> &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() {
|
|
qDebug() << "O1::link";
|
|
if (linked()) {
|
|
qDebug() << "O1::link: Linked already";
|
|
Q_EMIT linkingSucceeded();
|
|
return;
|
|
}
|
|
|
|
setLinked(false);
|
|
setToken("");
|
|
setTokenSecret("");
|
|
setExtraTokens(QVariantMap());
|
|
|
|
// Start reply server
|
|
if (!replyServer_->isListening())
|
|
replyServer_->listen(QHostAddress::Any, localPort());
|
|
|
|
// Get any query parameters for the request
|
|
#if QT_VERSION >= 0x050000
|
|
QUrlQuery requestData;
|
|
#else
|
|
QUrl requestData = requestTokenUrl();
|
|
#endif
|
|
O0RequestParameter param("", "");
|
|
foreach(param, requestParameters())
|
|
requestData.addQueryItem(QString(param.name), QUrl::toPercentEncoding(QString(param.value)));
|
|
|
|
// Get the request url and add parameters
|
|
#if QT_VERSION >= 0x050000
|
|
QUrl requestUrl = requestTokenUrl();
|
|
requestUrl.setQuery(requestData);
|
|
// Create request
|
|
QNetworkRequest request(requestUrl);
|
|
#else
|
|
// Create request
|
|
QNetworkRequest request(requestData);
|
|
#endif
|
|
|
|
// Create initial token request
|
|
QList<O0RequestParameter> headers;
|
|
headers.append(O0RequestParameter(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1()));
|
|
headers.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1()));
|
|
headers.append(O0RequestParameter(O2_OAUTH_NONCE, nonce()));
|
|
headers.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1()));
|
|
headers.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0"));
|
|
headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1()));
|
|
headers.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(headers, request, requestParameters(), 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<QNetworkReply *>(sender());
|
|
qWarning() << "O1::onTokenRequestError:" << (int)error << reply->errorString() << reply->readAll();
|
|
Q_EMIT linkingFailed();
|
|
}
|
|
|
|
void O1::onTokenRequestFinished() {
|
|
qDebug() << "O1::onTokenRequestFinished";
|
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
qDebug() << QString( "Request: %1" ).arg(reply->request().url().toString());
|
|
reply->deleteLater();
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qWarning() << "O1::onTokenRequestFinished: " << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
// Get request token and secret
|
|
QByteArray data = reply->readAll();
|
|
QMap<QString, QString> 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;
|
|
Q_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, callbackUrl().arg(replyServer_->serverPort()).toLatin1());
|
|
#else
|
|
QUrlQuery query(url);
|
|
query.addQueryItem(O2_OAUTH_TOKEN, requestToken_);
|
|
query.addQueryItem(O2_OAUTH_CALLBACK, callbackUrl().arg(replyServer_->serverPort()).toLatin1());
|
|
url.setQuery(query);
|
|
#endif
|
|
Q_EMIT openBrowser(url);
|
|
}
|
|
|
|
void O1::onVerificationReceived(QMap<QString, QString> params) {
|
|
qDebug() << "O1::onVerificationReceived";
|
|
Q_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";
|
|
Q_EMIT linkingFailed();
|
|
}
|
|
}
|
|
|
|
void O1::exchangeToken() {
|
|
qDebug() << "O1::exchangeToken";
|
|
|
|
// Create token exchange request
|
|
QNetworkRequest request(accessTokenUrl());
|
|
QList<O0RequestParameter> oauthParams;
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_CONSUMER_KEY, clientId().toLatin1()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_VERSION, "1.0"));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_TIMESTAMP, QString::number(QDateTime::currentDateTimeUtc().toTime_t()).toLatin1()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_NONCE, nonce()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_TOKEN, requestToken_.toLatin1()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_VERFIER, verifier_.toLatin1()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE_METHOD, signatureMethod().toLatin1()));
|
|
oauthParams.append(O0RequestParameter(O2_OAUTH_SIGNATURE, generateSignature(oauthParams, request, QList<O0RequestParameter>(), 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<QNetworkReply *>(sender());
|
|
qWarning() << "O1::onTokenExchangeError:" << (int)error << reply->errorString() << reply->readAll();
|
|
Q_EMIT linkingFailed();
|
|
}
|
|
|
|
void O1::onTokenExchangeFinished() {
|
|
qDebug() << "O1::onTokenExchangeFinished";
|
|
|
|
QNetworkReply *reply = qobject_cast<QNetworkReply *>(sender());
|
|
reply->deleteLater();
|
|
if (reply->error() != QNetworkReply::NoError) {
|
|
qWarning() << "O1::onTokenExchangeFinished: " << reply->errorString();
|
|
return;
|
|
}
|
|
|
|
// Get access token and secret
|
|
QByteArray data = reply->readAll();
|
|
QMap<QString, QString> 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);
|
|
}
|
|
setLinked(true);
|
|
Q_EMIT linkingSucceeded();
|
|
} else {
|
|
qWarning() << "O1::onTokenExchangeFinished: oauth_token or oauth_token_secret missing from response" << data;
|
|
Q_EMIT linkingFailed();
|
|
}
|
|
}
|
|
|
|
QMap<QString, QString> O1::parseResponse(const QByteArray &response) {
|
|
QMap<QString, QString> ret;
|
|
foreach (QByteArray param, response.split('&')) {
|
|
QList<QByteArray> 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();
|
|
}
|