OvhKvm/ovhapi.cpp

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());
}