diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.cc b/retroshare-gui/src/gui/settings/JsonApiPage.cc new file mode 100644 index 000000000..f01b6e345 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.cc @@ -0,0 +1,173 @@ +/* + * RetroShare JSON API + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#include "JsonApiPage.h" + +#include "rsharesettings.h" +#include "jsonapi/jsonapi.h" +#include "util/misc.h" + +#include +#include +#include + + +JsonApiPage::JsonApiPage(QWidget */*parent*/, Qt::WindowFlags /*flags*/) +{ + ui.setupUi(this); + connect( ui.addTokenPushButton, &QPushButton::clicked, + this, &JsonApiPage::addTokenClicked); + connect( ui.removeTokenPushButton, &QPushButton::clicked, + this, &JsonApiPage::removeTokenClicked ); + connect( ui.tokensListView, &QListView::clicked, + this, &JsonApiPage::tokenClicked ); + connect( ui.applyConfigPushButton, &QPushButton::clicked, + this, &JsonApiPage::onApplyClicked ); +} + +bool JsonApiPage::updateParams(QString &errmsg) +{ + bool ok = true; + bool changed = false; + + bool enabled = ui.enableCheckBox->isChecked(); + if( enabled != Settings->getJsonApiEnabled()) + { + Settings->setJsonApiEnabled(enabled); + changed = true; + } + + uint16_t port = ui.portSpinBox->value(); + if(port != Settings->getJsonApiPort()) + { + Settings->setJsonApiPort(port); + changed = true; + } + + QString listenAddress = ui.listenAddressLineEdit->text(); + if(listenAddress != Settings->getJsonApiListenAddress()) + { + Settings->setJsonApiListenAddress(listenAddress); + changed = true; + } + + if(changed) + { + checkShutdownJsonApi(); + ok = checkStartJsonApi(); + } + + if(!ok) errmsg = "Could not start JSON API Server!"; + return ok; +} + +void JsonApiPage::load() +{ + whileBlocking(ui.enableCheckBox)->setChecked(Settings->getJsonApiEnabled()); + whileBlocking(ui.portSpinBox)->setValue(Settings->getJsonApiPort()); + whileBlocking(ui.listenAddressLineEdit)->setText(Settings->getJsonApiListenAddress()); + whileBlocking(ui.tokensListView)->setModel(new QStringListModel(Settings->getJsonApiAuthTokens())); +} + +QString JsonApiPage::helpText() const { return ""; } + +/*static*/ bool JsonApiPage::checkStartJsonApi() +{ + checkShutdownJsonApi(); + + if(Settings->getJsonApiEnabled()) + { + jsonApiServer = new JsonApiServer( + Settings->getJsonApiPort(), + Settings->getJsonApiListenAddress().toStdString() ); + jsonApiServer->start("jsonApiServer"); + + for(const QString& token : Settings->getJsonApiAuthTokens()) + jsonApiServer->authorizeToken(token.toStdString()); + } + + return true; +} + +/*static*/ void JsonApiPage::checkShutdownJsonApi() +{ + if(jsonApiServer) + { + /* It is important to make a copy of +jsonApiServer+ pointer so the old + * object can be deleted later, while the original pointer is + * reassigned */ + JsonApiServer* oldJsonApiServer = jsonApiServer; + jsonApiServer = nullptr; + + oldJsonApiServer->shutdown(); + + QProgressDialog* pd = new QProgressDialog( + "Stopping JSON API Server", QString(), 0, 3000); + QTimer* prtm = new QTimer; + prtm->setInterval(16); // 60 FPS + connect( prtm, &QTimer::timeout, + pd, [=](){pd->setValue(pd->value()+16);} ); + pd->show(); + prtm->start(); + + /* Must wait for deletion because stopping of the server is async. + * It is important to capture a copy so it "survive" after + * safeStopJsonApiServer returns */ + QTimer::singleShot(3*1000, [=]() + { + delete oldJsonApiServer; + prtm->stop(); + pd->close(); + prtm->deleteLater(); + pd->deleteLater(); + }); + } +} + +void JsonApiPage::onApplyClicked(bool) +{ + QString errmsg; + updateParams(errmsg); +} + +void JsonApiPage::addTokenClicked(bool) +{ + QString token(ui.tokenLineEdit->text()); + if(jsonApiServer) jsonApiServer->authorizeToken(token.toStdString()); + QStringList newTk(Settings->getJsonApiAuthTokens()); + newTk.removeAll(token); + newTk.append(token); + Settings->setJsonApiAuthTokens(newTk); + whileBlocking(ui.tokensListView)->setModel(new QStringListModel(newTk)); +} + +void JsonApiPage::removeTokenClicked(bool) +{ + QString token(ui.tokenLineEdit->text()); + if(jsonApiServer) jsonApiServer->revokeAuthToken(token.toStdString()); + QStringList newTk(Settings->getJsonApiAuthTokens()); + newTk.removeAll(token); + Settings->setJsonApiAuthTokens(newTk); + whileBlocking(ui.tokensListView)->setModel( + new QStringListModel(Settings->getJsonApiAuthTokens()) ); +} + +void JsonApiPage::tokenClicked(const QModelIndex& index) +{ + ui.tokenLineEdit->setText(ui.tokensListView->model()->data(index).toString()); +} + diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.h b/retroshare-gui/src/gui/settings/JsonApiPage.h new file mode 100644 index 000000000..d7733ffb3 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.h @@ -0,0 +1,58 @@ +/* + * RetroShare JSON API + * Copyright (C) 2018 Gioacchino Mazzurco + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#pragma once + +#include +#include "ui_JsonApiPage.h" + +class JsonApiPage : public ConfigPage +{ + Q_OBJECT + +public: + + JsonApiPage(QWidget * parent = nullptr, Qt::WindowFlags flags = 0); + ~JsonApiPage() {} + + /** Loads the settings for this page */ + virtual void load(); + + virtual QPixmap iconPixmap() const + { return QPixmap(":/icons/svg/empty-circle.svg"); } + virtual QString pageName() const { return tr("JSON API"); } + virtual QString helpText() const; + + /** Call this after start of libretroshare/Retroshare + * checks the settings and starts JSON API if required */ + static bool checkStartJsonApi(); + + /** call this before shutdown of libretroshare + * it stops the JSON API if its running */ + static void checkShutdownJsonApi(); + +public slots: + void onApplyClicked(bool); + void addTokenClicked(bool); + void removeTokenClicked(bool); + void tokenClicked(const QModelIndex& index); + +private: + Ui::JsonApiPage ui; /// Qt Designer generated object + + bool updateParams(QString &errmsg); +}; diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.ui b/retroshare-gui/src/gui/settings/JsonApiPage.ui new file mode 100644 index 000000000..0408c8403 --- /dev/null +++ b/retroshare-gui/src/gui/settings/JsonApiPage.ui @@ -0,0 +1,147 @@ + + + JsonApiPage + + + + 0 + 0 + 521 + 393 + + + + Form + + + + + + + 274 + 0 + + + + JSON API Server + + + + + + Enable RetroShare JSON API Server + + + + + + + + + Port: + + + + + + + 1024 + + + 65535 + + + 9092 + + + + + + + + + + + Listen Address: + + + + + + + 127.0.0.1 + + + + + + + + + + + Token: + + + + + + + <html><head/><body><p>ApiUser:ApiPassword</p></body></html> + + + + + + + Add + + + + + + + Remove + + + + + + + + + Authenticated Tokens + + + + + + + + + + + + + Restart JSON API Server to apply settings + + + + + + + Qt::Vertical + + + + 17 + 632 + + + + + + + + + diff --git a/retroshare-gui/src/gui/settings/WebuiPage.cpp b/retroshare-gui/src/gui/settings/WebuiPage.cpp index 808900625..8051abcdd 100644 --- a/retroshare-gui/src/gui/settings/WebuiPage.cpp +++ b/retroshare-gui/src/gui/settings/WebuiPage.cpp @@ -23,11 +23,6 @@ resource_api::ApiServerLocal* WebuiPage::apiServerLocal = 0; #endif resource_api::RsControlModule* WebuiPage::controlModule = 0; -#ifdef RS_JSONAPI -# include - -JsonApiServer* WebuiPage::jsonApiServer = nullptr; -#endif // ifdef RS_JSONAPI WebuiPage::WebuiPage(QWidget */*parent*/, Qt::WindowFlags /*flags*/) { @@ -113,15 +108,6 @@ QString WebuiPage::helpText() const apiServerLocal = new resource_api::ApiServerLocal(apiServer, resource_api::ApiServerLocal::serverPath()); #endif -#ifdef RS_JSONAPI - // Use same port of libresapi + 2 - jsonApiServer = new JsonApiServer( - Settings->getWebinterfacePort() + 2, - Settings->getWebinterfaceAllowAllIps() ? "::" : "127.0.0.1", - [](int /*ec*/) { std::raise(SIGTERM); } ); - jsonApiServer->start("WebuiPage::jsonApiServer"); -#endif // ifdef RS_JSONAPI - return ok; } @@ -142,10 +128,6 @@ QString WebuiPage::helpText() const delete controlModule; controlModule = 0; } -#ifdef RS_JSONAPI - delete jsonApiServer; - jsonApiServer = nullptr; -#endif } /*static*/ void WebuiPage::showWebui() diff --git a/retroshare-gui/src/gui/settings/WebuiPage.h b/retroshare-gui/src/gui/settings/WebuiPage.h index 78961c98d..66af0327b 100644 --- a/retroshare-gui/src/gui/settings/WebuiPage.h +++ b/retroshare-gui/src/gui/settings/WebuiPage.h @@ -3,10 +3,6 @@ #include #include "ui_WebuiPage.h" -#ifdef RS_JSONAPI -# include "jsonapi/jsonapi.h" -#endif - namespace resource_api{ class ApiServer; class ApiServerMHD; @@ -59,7 +55,4 @@ private: static resource_api::ApiServerLocal* apiServerLocal; #endif static resource_api::RsControlModule* controlModule; -#ifdef RS_JSONAPI - static JsonApiServer* jsonApiServer; -#endif }; diff --git a/retroshare-gui/src/gui/settings/rsettingswin.cpp b/retroshare-gui/src/gui/settings/rsettingswin.cpp index 7f2dcf115..e04b21c18 100644 --- a/retroshare-gui/src/gui/settings/rsettingswin.cpp +++ b/retroshare-gui/src/gui/settings/rsettingswin.cpp @@ -50,6 +50,10 @@ # include "WebuiPage.h" #endif +#ifdef RS_JSONAPI +# include "JsonApiPage.h" +#endif + #define IMAGE_GENERAL ":/images/kcmsystem24.png" #define ITEM_SPACING 2 @@ -165,8 +169,12 @@ SettingsPage::initStackedWidget() #ifdef ENABLE_WEBUI addPage(new WebuiPage() ); #endif // ENABLE_WEBUI - // add widgets from plugins +#ifdef RS_JSONAPI + addPage(new JsonApiPage()); +#endif + + // add widgets from plugins for(int i=0;inbPlugins();++i) { RsPlugin *pl = rsPlugins->plugin(i) ; diff --git a/retroshare-gui/src/gui/settings/rsharesettings.cpp b/retroshare-gui/src/gui/settings/rsharesettings.cpp index e37216e39..744c41a8b 100644 --- a/retroshare-gui/src/gui/settings/rsharesettings.cpp +++ b/retroshare-gui/src/gui/settings/rsharesettings.cpp @@ -1171,3 +1171,45 @@ void RshareSettings::setPageAlreadyDisplayed(const QString& page_name,bool b) { return setValueToGroup("PageAlreadyDisplayed",page_name,b); } + +#ifdef RS_JSONAPI +bool RshareSettings::getJsonApiEnabled() +{ + return valueFromGroup("JsonApi", "enabled", false).toBool(); +} + +void RshareSettings::setJsonApiEnabled(bool enabled) +{ + setValueToGroup("JsonApi", "enabled", enabled); +} + +uint16_t RshareSettings::getJsonApiPort() +{ + return valueFromGroup("JsonApi", "port", 9092).toUInt(); +} + +void RshareSettings::setJsonApiPort(uint16_t port) +{ + setValueToGroup("JsonApi", "port", port); +} + +QString RshareSettings::getJsonApiListenAddress() +{ + return valueFromGroup("JsonApi", "listenAddress", "127.0.0.1").toString(); +} + +void RshareSettings::setJsonApiListenAddress(const QString& listenAddress) +{ + setValueToGroup("JsonApi", "listenAddress", listenAddress); +} + +QStringList RshareSettings::getJsonApiAuthTokens() +{ + return valueFromGroup("JsonApi", "authTokens", QStringList()).toStringList(); +} + +void RshareSettings::setJsonApiAuthTokens(const QStringList& authTokens) +{ + setValueToGroup("JsonApi", "authTokens", authTokens); +} +#endif // RS_JSONAPI diff --git a/retroshare-gui/src/gui/settings/rsharesettings.h b/retroshare-gui/src/gui/settings/rsharesettings.h index 0dfd3dc7f..b8a856b13 100644 --- a/retroshare-gui/src/gui/settings/rsharesettings.h +++ b/retroshare-gui/src/gui/settings/rsharesettings.h @@ -341,6 +341,20 @@ public: bool getPageAlreadyDisplayed(const QString& page_code) ; void setPageAlreadyDisplayed(const QString& page_code,bool b) ; +#ifdef RS_JSONAPI + bool getJsonApiEnabled(); + void setJsonApiEnabled(bool enabled); + + uint16_t getJsonApiPort(); + void setJsonApiPort(uint16_t port); + + QString getJsonApiListenAddress(); + void setJsonApiListenAddress(const QString& listenAddress); + + QStringList getJsonApiAuthTokens(); + void setJsonApiAuthTokens(const QStringList& authTokens); +#endif // ifdef RS_JSONAPI + protected: /** Default constructor. */ RshareSettings(); diff --git a/retroshare-gui/src/main.cpp b/retroshare-gui/src/main.cpp index 17c8ee507..ae0019e80 100644 --- a/retroshare-gui/src/main.cpp +++ b/retroshare-gui/src/main.cpp @@ -51,6 +51,10 @@ # include "gui/settings/WebuiPage.h" #endif +#ifdef RS_JSONAPI +# include "gui/settings/JsonApiPage.h" +#endif // RS_JSONAPI + #include "TorControl/TorManager.h" #include "TorControl/TorControlWindow.h" @@ -536,6 +540,10 @@ feenableexcept(FE_INVALID | FE_DIVBYZERO); WebuiPage::checkStartWebui(); #endif // ENABLE_WEBUI +#ifdef RS_JSONAPI + JsonApiPage::checkStartJsonApi(); +#endif // RS_JSONAPI + // This is done using a timer, because the passphrase request from notify is asynchrouneous and therefore clearing the // passphrase here makes it request for a passphrase when creating the default chat identity. @@ -545,6 +553,10 @@ feenableexcept(FE_INVALID | FE_DIVBYZERO); int ti = rshare.exec(); delete w ; +#ifdef RS_JSONAPI + JsonApiPage::checkShutdownJsonApi(); +#endif // RS_JSONAPI + #ifdef ENABLE_WEBUI WebuiPage::checkShutdownWebui(); #endif // ENABLE_WEBUI diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 7e9a5bda1..e6e75db9e 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -17,6 +17,12 @@ libresapihttpserver { FORMS *= gui/settings/WebuiPage.ui } +rs_jsonapi { + HEADERS *= gui/settings/JsonApiPage.h + SOURCES *= gui/settings/JsonApiPage.cc + FORMS *= gui/settings/JsonApiPage.ui +} + !include("../../libretroshare/src/use_libretroshare.pri"):error("Including") FORMS += TorControl/TorControlWindow.ui