173 lines
6.9 KiB
C++
173 lines
6.9 KiB
C++
#include "ovhapi.h"
|
|
#include <QDateTime>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkReply>
|
|
#include <QStringList>
|
|
#include <QCryptographicHash>
|
|
#include <QEventLoop>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QCoro/QCoroNetworkReply>
|
|
#include <QSqlDatabase>
|
|
#include <QSqlQuery>
|
|
#include <QSqlError>
|
|
#include <memory>
|
|
|
|
static const char* APPKEY = "e48f755fc0db87c0";
|
|
static const char* APPSECRET = "a81709def024dc175b82d6a87762bebf";
|
|
|
|
static const int initialOffset = 0x12345678;
|
|
|
|
|
|
OvhApi::OvhApi(const QString &baseUrl, QObject *parent)
|
|
: QObject{parent}, baseUrl{baseUrl}
|
|
{
|
|
timeOffset = initialOffset;
|
|
nam = new QNetworkAccessManager(this);
|
|
}
|
|
|
|
void OvhApi::setConsumerKey(const QString &ck) {
|
|
consumerKey = ck;
|
|
}
|
|
|
|
QCoro::Task<int> OvhApi::time()
|
|
{
|
|
if (timeOffset == initialOffset) {
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->get(QNetworkRequest(QUrl("https://eu.api.ovh.com/1.0/auth/time"))));
|
|
auto server_time = reply->readAll().toUInt();
|
|
timeOffset = QDateTime::currentSecsSinceEpoch() - server_time;
|
|
qDebug() << "time offset is " << timeOffset;
|
|
}
|
|
co_return int(QDateTime::currentSecsSinceEpoch() - timeOffset);
|
|
}
|
|
|
|
QCoro::Task<> OvhApi::completeRequest(const QString &method, const QByteArray &data, QNetworkRequest &request, bool auth)
|
|
{
|
|
if (!data.isEmpty())
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
request.setRawHeader("X-Ovh-Application", APPKEY);
|
|
if (auth)
|
|
co_await sign(method, data, request);
|
|
co_return;
|
|
}
|
|
|
|
QCoro::Task<> OvhApi::sign(const QString &method, const QByteArray &data, QNetworkRequest &request)
|
|
{
|
|
int timeStamp = co_await time();
|
|
// "$1$" + SHA1_HEX(AS+"+"+CK+"+"+METHOD+"+"+QUERY+"+"+BODY+"+"+TSTAMP)
|
|
QByteArray source = QStringList({APPSECRET, consumerKey, method, request.url().toString(), QString::fromUtf8(data), QString::number(timeStamp)})
|
|
.join("+")
|
|
.toLatin1();
|
|
|
|
auto hash = QCryptographicHash::hash(source, QCryptographicHash::Sha1).toHex();
|
|
hash.prepend("$1$");
|
|
request.setRawHeader("X-Ovh-Consumer", consumerKey.toLatin1());
|
|
request.setRawHeader("X-Ovh-Timestamp", QByteArray::number(timeStamp));
|
|
request.setRawHeader("X-Ovh-Signature", hash);
|
|
co_return;
|
|
}
|
|
|
|
QCoro::Task<QByteArray> OvhApi::download(const QUrl &url)
|
|
{
|
|
QNetworkRequest request(url);
|
|
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->get(request));
|
|
QByteArray rawAnswer = reply->readAll();
|
|
qDebug() << reply->rawHeaderPairs();
|
|
qDebug() << reply->header(QNetworkRequest::ContentTypeHeader) << rawAnswer.size();
|
|
qDebug() << reply->url();
|
|
co_return rawAnswer;
|
|
}
|
|
|
|
QCoro::Task<QJsonDocument> OvhApi::get(const QString &path, std::chrono::seconds cacheDuration, bool forceRefresh)
|
|
{
|
|
if (cacheDuration.count() > 0 && !forceRefresh) {
|
|
// qDebug() << "Gonna look at the cache";
|
|
QSqlDatabase db = QSqlDatabase::database();
|
|
if (db.isValid()) {
|
|
// qDebug() << "Cache check";
|
|
// Check from cache first
|
|
auto cacheQuery = QSqlQuery{"SELECT data FROM cache WHERE ck=? AND path=? AND expiration>?", db};
|
|
cacheQuery.bindValue(0, consumerKey);
|
|
cacheQuery.bindValue(1, path);
|
|
cacheQuery.bindValue(2, QDateTime::currentDateTime());
|
|
if (!cacheQuery.exec()) {
|
|
qDebug() << "Failed to execute SELECT in cache" << cacheQuery.lastError().text();
|
|
} else {
|
|
if (cacheQuery.next()) {
|
|
auto answer = QJsonDocument::fromJson(cacheQuery.value(0).toByteArray());
|
|
co_return answer;
|
|
} else {
|
|
qDebug() << "No cache for " << path;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QNetworkRequest request(QUrl{baseUrl + path});
|
|
co_await completeRequest("GET", "", request);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->get(request));
|
|
QByteArray rawAnswer = reply->readAll();
|
|
auto answer = QJsonDocument::fromJson(rawAnswer);
|
|
|
|
if (cacheDuration.count() > 0 || forceRefresh) {
|
|
// qDebug() << "Gonna store in the cache";
|
|
QSqlDatabase db = QSqlDatabase::database();
|
|
if (db.isValid()) {
|
|
// Check from cache first
|
|
auto cacheQuery = QSqlQuery{"INSERT INTO cache (ck, path, expiration, data) VALUES (?, ?, ?, ?) ON CONFLICT DO UPDATE SET expiration=excluded.expiration, data=excluded.data;", db};
|
|
cacheQuery.bindValue(0, consumerKey);
|
|
cacheQuery.bindValue(1, path);
|
|
cacheQuery.bindValue(2, QDateTime::currentDateTime().addSecs(cacheDuration.count()));
|
|
cacheQuery.bindValue(3, rawAnswer);
|
|
if (!cacheQuery.exec()) {
|
|
qDebug() << "Failed to execute UPSERT in cache" << cacheQuery.lastError().text();
|
|
}
|
|
}
|
|
}
|
|
co_return answer;
|
|
}
|
|
|
|
QCoro::Task<QJsonDocument> OvhApi::post(const QString &path, const QByteArray &data)
|
|
{
|
|
QNetworkRequest request(QUrl{baseUrl + path});
|
|
co_await completeRequest("POST", data, request);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->post(request, data));
|
|
auto answer = QJsonDocument::fromJson(reply->readAll());
|
|
co_return answer;
|
|
}
|
|
|
|
QCoro::Task<QJsonDocument> OvhApi::put(const QString &path, const QByteArray &data)
|
|
{
|
|
QNetworkRequest request(QUrl{baseUrl + path});
|
|
co_await completeRequest("PUT", data, request);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->put(request, data));
|
|
auto answer = QJsonDocument::fromJson(reply->readAll());
|
|
co_return answer;
|
|
}
|
|
|
|
QCoro::Task<> OvhApi::deleteResource(const QString &path)
|
|
{
|
|
QNetworkRequest request(QUrl{baseUrl + path});
|
|
co_await completeRequest("DELETE", "", request);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->deleteResource(request));
|
|
auto answer = QJsonDocument::fromJson(reply->readAll());
|
|
co_return;
|
|
}
|
|
|
|
QCoro::Task<std::tuple<QString, QUrl>> OvhApi::requestCredentials(const QUrl &callback)
|
|
{
|
|
QNetworkRequest request(QUrl("https://eu.api.ovh.com/1.0/auth/credential"));
|
|
QByteArray accessRules{"{\"accessRules\": [{\"method\": \"GET\", \"path\": \"/*\"},{\"method\": \"POST\", \"path\": \"/*\"},{\"method\": \"PUT\", \"path\": \"/*\"},{\"method\": \"DELETE\", \"path\": \"/*\"}]}"};
|
|
qDebug() << QJsonDocument::fromJson(accessRules);
|
|
co_await completeRequest("POST",
|
|
accessRules,
|
|
request,
|
|
false);
|
|
std::unique_ptr<QNetworkReply> reply(co_await nam->post(request, accessRules));
|
|
qDebug() << reply->errorString();
|
|
auto rawAnswer = reply->readAll();
|
|
qDebug() << rawAnswer;
|
|
auto answer = QJsonDocument::fromJson(rawAnswer);
|
|
co_return std::make_tuple(answer.object()["consumerKey"].toString(), answer.object()["validationUrl"].toString());
|
|
}
|