Some work on Generic Nextcloud API class

This commit is contained in:
Scharel Clemens 2021-01-25 22:42:38 +01:00
parent bd116a8c15
commit 538bc6c54f
2 changed files with 311 additions and 17 deletions

View file

@ -1,6 +1,297 @@
#include "nextcloudapi.h"
#include <QGuiApplication>
NextcloudApi::NextcloudApi(QObject *parent) : QObject(parent)
{
// Initial status
setStatus(NextcloudStatus::NextcloudUnknown);
setCababilitiesStatus(CapabilitiesStatus::CapabilitiesUnknown);
setLoginStatus(LoginStatus::LoginUnknown);
m_status_installed = false;
m_status_maintenance = false;
m_status_needsDbUpgrade = false;
m_status_extendedSupport = false;
m_running_requests = 0;
// Login Flow V2 poll timer
m_loginPollTimer.setInterval(LOGIN_FLOWV2_POLL_INTERVALL);
connect(&m_loginPollTimer, SIGNAL(timeout()), this, SLOT(pollLoginUrl()));
// Verify URL
connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(verifyUrl(QUrl)));
// Listen to signals of the QNetworkAccessManager class
connect(&m_manager, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(requireAuthentication(QNetworkReply*,QAuthenticator*)));
connect(&m_manager, SIGNAL(networkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)), this, SLOT(onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility)));
connect(&m_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
connect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>)));
// Prepare the QNetworkRequest classes
m_request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
m_request.setHeader(QNetworkRequest::UserAgentHeader, QGuiApplication::applicationDisplayName() + " " + QGuiApplication::applicationVersion() + " - " + QSysInfo::machineHostName());
m_request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/x-www-form-urlencoded").toUtf8());
m_request.setRawHeader("OCS-APIRequest", "true");
m_request.setRawHeader("Accept", "application/json");
m_authenticatedRequest = m_request;
m_authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json").toUtf8());
}
NextcloudApi::~NextcloudApi() {
}
void NextcloudApi::setVerifySsl(bool verify) {
if (verify != (m_request.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer) ||
verify != (m_authenticatedRequest.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer)) {
m_request.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone);
m_authenticatedRequest.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone);
emit verifySslChanged(verify);
}
}
void NextcloudApi::setUrl(QUrl url) {
if (url != m_url) {
QString oldServer = server();
setScheme(url.scheme());
setHost(url.host());
setPort(url.port());
setUsername(url.userName());
setPassword(url.password());
setPath(url.path());
if (server() != oldServer)
emit serverChanged(server());
emit urlChanged(m_url);
}
}
QString NextcloudApi::server() const {
QUrl server;
server.setScheme(m_url.scheme());
server.setHost(m_url.host());
if (m_url.port() > 0)
server.setPort(m_url.port());
server.setPath(m_url.path());
return server.toString();
}
void NextcloudApi::setServer(QString serverUrl) {
QUrl url(serverUrl.trimmed());
if (url != server()) {
setScheme(url.scheme());
setHost(url.host());
setPort(url.port());
setPath(url.path());
}
}
void NextcloudApi::setScheme(QString scheme) {
if (scheme != m_url.scheme() && (scheme == "http" || scheme == "https")) {
m_url.setScheme(scheme);
emit schemeChanged(m_url.scheme());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
void NextcloudApi::setHost(QString host) {
if (host != m_url.host()) {
m_url.setHost(host);
emit hostChanged(m_url.host());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
void NextcloudApi::setPort(int port) {
if (port != m_url.port() && port >= 1 && port <= 65535) {
m_url.setPort(port);
emit portChanged(m_url.port());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
void NextcloudApi::setUsername(QString username) {
if (username != m_url.userName()) {
m_url.setUserName(username);
QString concatenated = username + ":" + password();
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
m_authenticatedRequest.setRawHeader("Authorization", headerData.toLocal8Bit());
emit usernameChanged(m_url.userName());
emit urlChanged(m_url);
}
}
void NextcloudApi::setPassword(QString password) {
if (password != m_url.password()) {
m_url.setPassword(password);
QString concatenated = username() + ":" + password;
QByteArray data = concatenated.toLocal8Bit().toBase64();
QString headerData = "Basic " + data;
m_authenticatedRequest.setRawHeader("Authorization", headerData.toLocal8Bit());
emit passwordChanged(m_url.password());
emit urlChanged(m_url);
}
}
void NextcloudApi::setPath(QString path) {
if (path != m_url.path()) {
m_url.setPath(path);
emit pathChanged(m_url.path());
emit serverChanged(server());
emit urlChanged(m_url);
}
}
const QString NextcloudApi::errorMessage(int error) const {
QString message;
switch (error) {
case NoError:
message = tr("No error");
break;
case NoConnectionError:
message = tr("No network connection available");
break;
case CommunicationError:
message = tr("Failed to communicate with the Nextcloud server");
break;
case SslHandshakeError:
message = tr("An error occured while establishing an encrypted connection");
break;
case AuthenticationError:
message = tr("Could not authenticate to the Nextcloud instance");
break;
default:
message = tr("Unknown error");
break;
}
return message;
}
bool NextcloudApi::get(const QString& endpoint, bool authenticated) {
QUrl url = server();
url.setPath(url.path() + endpoint);
qDebug() << "GET" << url.toDisplayString();
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
QNetworkRequest request = authenticated ? m_authenticatedRequest : m_request;
request.setUrl(url);
m_manager.get(request);
m_running_requests ++;
return true;
}
else {
qDebug() << "GET URL not valid" << url.toDisplayString();
}
return false;
}
bool NextcloudApi::post(const QString& endpoint, const QByteArray& data, bool authenticated) {
QUrl url = server();
url.setPath(url.path() + endpoint);
qDebug() << "POST" << url.toDisplayString();
qDebug() << data;
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
QNetworkRequest request = authenticated ? m_authenticatedRequest : m_request;
request.setUrl(url);
m_manager.post(request, data);
m_running_requests ++;
return true;
}
else {
qDebug() << "POST URL not valid" << url.toDisplayString();
}
return false;
}
bool NextcloudApi::put(const QString& endpoint, const QByteArray& data, bool authenticated) {
QUrl url = server();
url.setPath(url.path() + endpoint);
qDebug() << "PUT" << url.toDisplayString();
qDebug() << data;
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
QNetworkRequest request = authenticated ? m_authenticatedRequest : m_request;
request.setUrl(url);
m_manager.put(request, data);
m_running_requests ++;
return true;
}
else {
qDebug() << "PUT URL not valid" << url.toDisplayString();
}
return false;
}
bool NextcloudApi::del(const QString& endpoint, bool authenticated) {
QUrl url = server();
url.setPath(url.path() + endpoint);
qDebug() << "DEL" << url.toDisplayString();
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
QNetworkRequest request = authenticated ? m_authenticatedRequest : m_request;
request.setUrl(url);
m_manager.deleteResource(request);
m_running_requests ++;
return true;
}
else {
qDebug() << "DEL URL not valid" << url.toDisplayString();
}
return false;
}
bool NextcloudApi::getStatus() {
if (get(STATUS_ENDPOINT, false)) {
setStatus(NextcloudStatus::NextcloudBusy);
return true;
}
else {
setStatus(NextcloudStatus::NextcloudFailed);
}
return false;
}
bool NextcloudApi::initiateFlowV2Login() {
if (m_loginStatus == LoginStatus::LoginFlowV2Initiating || m_loginStatus == LoginStatus::LoginFlowV2Polling) {
abortFlowV2Login();
}
if (post(LOGIN_FLOWV2_ENDPOINT, QByteArray(), false)) {
setLoginStatus(LoginStatus::LoginFlowV2Initiating);
return true;
}
else {
setLoginStatus(LoginStatus::LoginFlowV2Failed);
abortFlowV2Login();
}
return false;
}
void NextcloudApi::abortFlowV2Login() {
m_loginPollTimer.stop();
m_loginUrl.clear();
m_pollUrl.clear();
m_pollToken.clear();
setLoginStatus(LoginStatus::LoginUnknown);
}
bool NextcloudApi::verifyLogin() {
return get(USER_CAPABILITIES_ENDPOINT.arg(username()), true);
}
bool NextcloudApi::getAppPassword() {
return get(GET_APPPASSWORD_ENDPOINT, true);
}
bool NextcloudApi::deleteAppPassword() {
return del(DEL_APPPASSWORD_ENDPOINT, true);
}
bool NextcloudApi::pollLoginUrl() {
if (post(m_pollUrl.path(), QByteArray("token=").append(m_pollToken), false)) {
setLoginStatus(LoginStatus::LoginFlowV2Polling);
return true;
}
else {
setLoginStatus(LoginStatus::LoginFlowV2Failed);
abortFlowV2Login();
}
return false;
}

View file

@ -34,7 +34,7 @@ class NextcloudApi : public QObject
Q_PROPERTY(bool verifySsl READ verifySsl WRITE setVerifySsl NOTIFY verifySslChanged) // to allow selfsigned certificates
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) // complete API URL = <scheme>://<username>:<password>@<host>[:<port>]/<path>
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged) // url without username and password = <scheme>://<host>[:<port>]/<path>
// the following six properties will update the url and server properties
// the following six properties will update the url and server properties and vice versa
Q_PROPERTY(QString scheme READ scheme WRITE setScheme NOTIFY schemeChanged)
Q_PROPERTY(QString host READ host WRITE setHost NOTIFY hostChanged)
Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged)
@ -43,12 +43,13 @@ class NextcloudApi : public QObject
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
// Networking status information
Q_PROPERTY(bool ready READ ready NOTIFY readyChanged)
Q_PROPERTY(bool urlValid READ urlValid NOTIFY urlValidChanged)
Q_PROPERTY(bool networkAccessible READ networkAccessible NOTIFY networkAccessibleChanged)
Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
// Nextcloud status (status.php), these properties will be automatically updated on changes of the generic properties
Q_PROPERTY(NextcloudStatus ncStatusStatus READ ncStatusStatus NOTIFY ncStatusStatusChanged)
Q_PROPERTY(NextcloudStatus statusStatus READ statusStatus NOTIFY statusStatusChanged)
Q_PROPERTY(bool statusInstalled READ statusInstalled NOTIFY statusInstalledChanged)
Q_PROPERTY(bool statusMaintenance READ statusMaintenance NOTIFY statusMaintenanceChanged)
Q_PROPERTY(bool statusNeedsDbUpgrade READ statusNeedsDbUpgrade NOTIFY statusNeedsDbUpgradeChanged)
@ -126,12 +127,13 @@ public:
void setPassword(QString password);
// Class status information
bool ready() const { return urlValid() && networkAccessible() && !busy() && statusInstalled() && !statusMaintenance() && loginStatus() == LoginSuccess; }
bool urlValid() const { return m_url.isValid(); }
bool networkAccessible() const { return m_manager.networkAccessible() == QNetworkAccessManager::Accessible; }
bool busy() const;
bool busy() const { return m_running_requests > 0; }
// Nextcloud status (status.php)
NextcloudStatus ncStatusStatus() const { return m_statusStatus; }
NextcloudStatus statusStatus() const { return m_statusStatus; }
bool statusInstalled() const { return m_status_installed; }
bool statusMaintenance() const { return m_status_maintenance; }
bool statusNeedsDbUpgrade() const { return m_status_needsDbUpgrade; }
@ -159,20 +161,20 @@ public:
Q_INVOKABLE const QString errorMessage(int error) const;
public slots:
// Callable functions
Q_INVOKABLE bool getNcStatus();
Q_INVOKABLE bool initiateFlowV2Login();
Q_INVOKABLE void abortFlowV2Login();
Q_INVOKABLE void verifyLogin();
Q_INVOKABLE void getAppPassword();
Q_INVOKABLE void deleteAppPassword();
// API helper functions
Q_INVOKABLE bool get(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool post(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool put(const QString& endpoint, bool authenticated = true);
Q_INVOKABLE bool post(const QString& endpoint, const QByteArray& data, bool authenticated = true);
Q_INVOKABLE bool put(const QString& endpoint, const QByteArray& data, bool authenticated = true);
Q_INVOKABLE bool del(const QString& endpoint, bool authenticated = true);
// Callable functions
Q_INVOKABLE bool getStatus();
Q_INVOKABLE bool initiateFlowV2Login();
Q_INVOKABLE void abortFlowV2Login();
Q_INVOKABLE bool verifyLogin();
Q_INVOKABLE bool getAppPassword();
Q_INVOKABLE bool deleteAppPassword();
signals:
// Generic API properties
void verifySslChanged(bool verify);
@ -194,7 +196,7 @@ signals:
void capabilitiesStatusChanged(CapabilitiesStatus status);
// Nextcloud status (status.php)
void ncStatusStatusChanged(NextcloudStatus status);
void statusChanged(NextcloudStatus status);
void statusInstalledChanged(bool installed);
void statusMaintenanceChanged(bool maintenance);
void statusNeedsDbUpgradeChanged(bool needsDbUpgrade);
@ -219,7 +221,7 @@ private slots:
void requireAuthentication(QNetworkReply * reply, QAuthenticator * authenticator);
void onNetworkAccessibleChanged(QNetworkAccessManager::NetworkAccessibility accessible);
void replyFinished(QNetworkReply* reply);
void pollLoginUrl();
bool pollLoginUrl();
void sslError(QNetworkReply* reply, const QList<QSslError> &errors);
private:
@ -227,10 +229,11 @@ private:
QNetworkAccessManager m_manager;
QNetworkRequest m_request;
QNetworkRequest m_authenticatedRequest;
uint m_running_requests;
// Nextcloud status.php
void updateStatus(const QJsonObject &status);
void setNextcloudStatus(NextcloudStatus status, bool *changed = NULL);
void setStatus(NextcloudStatus status, bool *changed = NULL);
NextcloudStatus m_statusStatus;
bool m_status_installed;
bool m_status_maintenance;